阻止requestAnimationFrame一直运行

时间:2013-12-18 10:18:15

标签: javascript jquery css html5-canvas requestanimationframe

我想知道如何通过animate调用requestAnimationFrame函数,只有在真正需要它时才会这样做。目前,animate始终被调用,这会产生一些开销。

我已在我的animate函数中尝试比较targetRadius和初始radius,并在它们相同时返回false。不幸的是,这根本不起作用。

有人可以解释一下如何解决这个问题吗?

jsfiddle

HTML:

  <canvas id="ddayCanvas" width="288" height="288" data-image="http://www.topdesignmag.com/wp-content/uploads/2011/07/64.png">
    <div>
        <div class="product-image"></div>
        <div class="product-box">...</div>
        <a href="#" class="overlay">...</a>
    </div>    
  </canvas>

JS:

// Options
var maxImageWidth = 250,
    maxImageHeight = 196;

var canvas = $('#ddayCanvas'),
    canvasWidth = canvas.width(),
    canvasHeight = canvas.height(),
    sectorColor = $('.product-box').css('background-color'),
    context = canvas[0].getContext('2d'),
    imageSrc = canvas.data('image'),
    imageObj = new Image(),
    imageWidth, imageHeight,
    mouseover = false;

    imageObj.onload = function() {
        imageWidth = this.width;
        imageHeight = this.height;

        if (imageWidth > maxImageWidth){
            imageHeight = imageHeight - (imageWidth - maxImageWidth);
            imageWidth = maxImageWidth;
        }

        if (imageHeight > maxImageHeight) {
            imageWidth = imageWidth - (imageHeight - maxImageHeight);
            imageHeight = maxImageHeight;
        }

        drawDday(90); 
    };

    imageObj.src = imageSrc;  

function drawDday (radius) {
    context.clearRect(0, 0, canvasWidth, canvasHeight);
    context.drawImage(imageObj, Math.ceil((canvasWidth - imageWidth) / 2), Math.ceil((canvasHeight - imageHeight) / 2), imageWidth, imageHeight);
    context.fillStyle = sectorColor;
    context.beginPath();
    context.rect(0, 0, canvasWidth, canvasHeight);
    context.arc(canvasWidth/2, canvasHeight/2, radius, 0, Math.PI*2, true);
    context.closePath();
    context.fill();

    // Check out the console
    console.log('test');
}


var radius = baseRadius = 90,
    targetRadius = 110,
    ease = 50,
    speed = 2;

function animate(){
    if(mouseover){
        radius += ((targetRadius-radius)/ease)*speed;
    } else {
        radius -= ((radius-baseRadius)/ease)*speed;
    }
    if(radius > targetRadius) radius = targetRadius;
    if(radius < baseRadius) radius = baseRadius;

    drawDday(radius);   
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

canvas.on('mouseover', function(e){
    mouseover = true;
}).on('mouseout', function(){
    mouseover = false;
});

2 个答案:

答案 0 :(得分:5)

您需要实现条件,以便打破循环,例如(根据需要采用):

var isRunning = true;

function loop() {

    ... funky stuff here ...

    /// test condition before looping
    if (isRunning) requestAnimationFrame(loop);
}

现在,当您将isRunning设置为false时,循环将会中断。为方便起见,建议您使用方法来启动和停止循环:

function startLoop(state) {

    if (state && !isRunning) {
        isRunning = true;
        loop();             /// starts loop

    } else if (!state && isRunning) {
        isRunning = false;
    }
}

条件可以由你需要设置的任何东西来设置,例如在动画完成后的回调等等。重要的部分是条件标志可用于使用它的两个范围(即最常见的)在全球范围内)。

<强>更新

在这种情况下更具体的是你的条件(半径)永远不会达到最终停止循环所需的条件。

您可以采取以下措施来解决此问题:

DEMO

var isPlaying = false;

function animate(){
    /**
     * To make sure you will reach the condition required you need
     * to either make sure you have a fall-out for the steps or the
     * step will become 0 not adding/subtracting anything so your
     * checks below won't trigger. Here we can use a simple max of
     * the step and a static value to make sure the value is always > 0
    */
    if(mouseover){
        radius += Math.max( ((targetRadius-radius)/ease)*speed, 0.5);
    } else {
        radius -= Math.max( ((radius-baseRadius)/ease)*speed,   0.5);
    }

    /**
     * Now the checks will trigger properly and we can use the
     * isPlaying flag to stop the loop when targets are reached.
    */
    if(radius >= targetRadius) {
        radius = targetRadius;
        isPlaying = false;              /// stop loop after this
    } else if (radius <= baseRadius) {
        radius = baseRadius;
        isPlaying = false;              /// stop loop after this
    }

    drawDday(radius);

    /// loop?
    if (isPlaying === true) requestAnimationFrame(animate);
}

为了触发循环,我们使用一种方法来检查循环是否正在运行,如果不是,它将重置isPlaying标志并启动循环。我们在mouseovermouseout

中执行此操作
canvas.on('mouseover', function(e){
    mouseover = true;
    startAnim();

}).on('mouseout', function(){
    mouseover = false;
    startAnim();
});

该方法只是检查isPlaying,如果没有设置,则将其设置为true并启动循环 - 这样循环只启动一次:

function startAnim() {
    if (!isPlaying) {
        isPlaying = true;
        requestAnimationFrame(animate);
    }
}

在演示中,我添加了控制台日志记录,以显示循环何时运行以及何时命中目标。

希望这有帮助。

答案 1 :(得分:3)

您的animate功能被连续呼叫的原因是,您首先拨打requestAnimationFrame(animate);,然后每次拨打animate,再次无条件呼叫requestAnimationFrame(animate);。除非你在某个时刻使用cancelAnimationFrame(你没有),否则这个周期永远不会被打破,或者确保animate仅在需要时请求另一个帧。

另一个问题是,radius目前永远不会到达targetRadiusbaseRadius,因此以下任何一项都不会成立:

if(radius > targetRadius) radius = targetRadius;
if(radius < baseRadius) radius = baseRadius;

这不是对animate的持续调用的直接责任,但由于targetRadiusbaseRadius用于指示动画的结束点,因此我们需要形成他们有某种明智的条件。

所以,你可以这样做:http://jsfiddle.net/PLDUq/9/

var radius = baseRadius = 50,
    targetRadius = 110,
    ease = 50,
    speed = 12,
    currentAnim;

function animate(){
    if(mouseover){
        radius += ((targetRadius-radius)/ease)*speed;
    } else {
        radius -= ((radius-baseRadius)/ease)*speed;
    }

    drawDday(radius);

    if(Math.round(radius) >= targetRadius) {
        // uses Math.round() to ensure condition can be fulfilled

        radius = targetRadius;
        return; // doesn't call next frame
    }
    if(Math.round(radius) <= baseRadius) {
        radius = baseRadius;
        return; // doesn't call next frame
    }

    requestAnimationFrame(animate);
}

canvas.on('mouseenter mouseleave', function (e) {
    if (currentAnim) {requestAnimationFrame(currentAnim);}
    // cancels current animation if one is playing to
    // prevent several concurrent loops calling animate()

    mouseover = (e.type === 'mouseenter');

    requestAnimationFrame(animate);
});