防止 requestAnimationFrame 一直运行

我想知道如何仅在真正需要时才通过requestAnimationFrame 调用animate 函数.目前 animate 一直被调用,我猜这会产生开销.

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

谁能解释一下如何解决这个问题?

jsfiddle

HTML:

 <div class="product-image"></div><div class="product-box">...</div><a href="#" class="overlay">...</a></div></画布>

JS:

//选项var maxImageWidth = 250,最大图像高度 = 196;var canvas = $('#ddayCanvas'),canvasWidth = canvas.width(),canvasHeight = canvas.height(),扇区颜色 = $('.product-box').css('背景颜色'),上下文 = 画布 [0].getContext('2d'),imageSrc = canvas.data('image'),imageObj = 新图像(),图像宽度,图像高度,鼠标悬停=假;imageObj.onload = 函数() {imageWidth = this.width;imageHeight = this.height;if (imageWidth > maxImageWidth){imageHeight = imageHeight - (imageWidth - maxImageWidth);图像宽度 = 最大图像宽度;}if (imageHeight > maxImageHeight) {imageWidth = imageWidth - (imageHeight - maxImageHeight);图像高度 = 最大图像高度;}drawDday(90);};imageObj.src = imageSrc;函数drawDday(半径){context.clearRect(0, 0, canvasWidth, canvasHeight);context.drawImage(imageObj, Math.ceil((canvasWidth - imageWidth)/2), Math.ceil((canvasHeight - imageHeight)/2), imageWidth, imageHeight);context.fillStyle = 扇区颜色;context.beginPath();context.rect(0, 0, canvasWidth, canvasHeight);context.arc(canvasWidth/2, canvasHeight/2, radius, 0, Math.PI*2, true);context.closePath();context.fill();//查看控制台console.log('test');}变量半径 = baseRadius = 90,目标半径 = 110,轻松= 50,速度 = 2;函数动画(){如果(鼠标悬停){半径 += ((targetRadius-radius)/ease)*速度;} 别的 {半径 -= ((radius-baseRadius)/ease)*速度;}如果(半径 > 目标半径)半径 = 目标半径;if(radius < baseRadius) 半径 = baseRadius;drawDday(半径);requestAnimationFrame(动画);}requestAnimationFrame(动画);canvas.on('鼠标悬停', function(e){鼠标悬停=真;}).on('mouseout', function(){鼠标悬停=假;});

解决方案

你需要实现一个条件,这样你就可以打破循环,例如(根据需要采用):

var isRunning = true;函数循环(){......这里有时髦的东西......///循环前的测试条件if (isRunning) requestAnimationFrame(loop);}

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

函数 startLoop(state) {if (state && !isRunning) {isRunning = true;环形();///开始循环} else if (!state && isRunning) {isRunning = 假;}}

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

更新:

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

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

演示

var isPlaying = false;函数动画(){/*** 确保您达到所需的条件* 以确保您对这些步骤或* step 将变为 0 不添加/减去任何东西,所以你* 下面的检查不会触发.在这里我们可以使用一个简单的最大值* 步骤和静态值,以确保该值始终为 >0*/如果(鼠标悬停){半径 += Math.max( ((targetRadius-radius)/ease)*speed, 0.5);} 别的 {半径 -= Math.max( ((radius-baseRadius)/ease)*speed, 0.5);}/*** 现在检查将正确触发,我们可以使用* isPlaying 标志在达到目标时停止循环.*/如果(半径>=目标半径){半径=目标半径;正在播放 = 假;///在此之后停止循环} else if (radius <= baseRadius) {半径=基半径;正在播放 = 假;///在此之后停止循环}drawDday(半径);///环形?if (isPlaying === true) requestAnimationFrame(animate);}

为了触发循环,我们使用一种方法来检查循环是否正在运行,如果没有,它将重置 isPlaying 标志并启动循环.我们在 mouseovermouseout 中都这样做:

canvas.on('mouseover', function(e){鼠标悬停=真;开始动画();}).on('mouseout', function(){鼠标悬停=假;开始动画();});

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

函数 startAnim() {如果(!isPlaying){isPlaying = 真;requestAnimationFrame(动画);}}

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

希望这会有所帮助.

I'd like to know how to call the animate function through requestAnimationFrame only when it's realy needed. Currently the animate is called all the time what generates an overhead I guess.

I already tried inside my animate function to compare targetRadius and the inital radius and return false once they are the same. Unfortunately this doesn't work at all.

Can someone explain me how to solve that?

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;
});

解决方案

You need to implement a condition so you can break the loop, for example (adopt as needed):

var isRunning = true;

function loop() {

    ... funky stuff here ...

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

Now when you set isRunning to false the loop will break. For convenience it's recommended that you have a method to start and stop the loop:

function startLoop(state) {

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

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

The condition can be set by anything you need it to be set by, for example on a callback after an animation has finished etc. The important part is that the condition flag is available to both scopes using it (ie. most commonly in the global scope).

UPDATE:

More specific in this case is that your condition (radius) will never reach the condition required to eventually stop the loop.

Here is what you can do to fix this:

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);
}

In order to trigger the loop we use a method that will check if the loop is running, if not it will reset the isPlaying flag and start the loop. We do this inside both mouseover and mouseout:

canvas.on('mouseover', function(e){
    mouseover = true;
    startAnim();

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

The method is simply checking isPlaying and if not set it set it to true and starts the loop - this so that the loop is only started once:

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

In the demo I added console logging to show when the loop is running and when targets are hit.

Hope this helps.

相关文章