阻止requestAnimationFrame一直运行

我想知道如何通过requestAnimationFrame调用animate函数,只有当它真的需要它时。 目前, animate被调用,这会产生开销,我猜。

我已经在我的animate函数内部尝试比较targetRadius和初始radius并且一旦它们相同就返回false。 不幸的是,这根本不起作用。

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

的jsfiddle

HTML:

   
...
...

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

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

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

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

希望这可以帮助。

连续调用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); });