如何使用requestAnimationFrame?

52

我对动画编程不太熟悉,但最近使用了setTimeout创建了一个动画。帧率过低,因此我找到了一种解决方案使用requestAnimationFrame,在这个链接中有说明。

目前,我的代码如下:

//shim layer with setTimeout fallback
    window.requestAnimFrame = (function(){
        return  
            window.requestAnimationFrame       || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame    || 
            window.oRequestAnimationFrame      || 
            window.msRequestAnimationFrame     || 
            function(/* function */ callback){
                window.setTimeout(callback, 1000 / 60);
            };
    })();
    (function animloop(){
        //Get metrics
        var leftCurveEndX = finalLeft - initialLeft;
        var leftCurveEndY = finalTop + finalHeight - initialTop;
        var rightCurveEndX = finalLeft + finalWidth - initialLeft - initialWidth;
        var rightCurveEndY = leftCurveEndY;

        chopElement(0, 0, 0, 0, leftCurveEndX, leftCurveEndY, rightCurveEndX, rightCurveEndY);//Creates a new frame 
        requestAnimFrame(animloop);
    })();

这在第一帧时停止。我在chopElement函数中有一个回调函数requestAnimFrame(animloop)

另外,是否有更详细的使用此API的指南?


3
一般来说,在JS中要非常小心地处理返回值,如果你删除return后面的换行符,那么这段代码就能正常工作。 - Rich Bradshaw
3个回答

83

警告!这个问题不是关于最佳方式来模拟requestAnimFrame的。如果你在寻找答案,请查看本页其他任意答案。


你被自动分号插入骗了。尝试这样做:

window.requestAnimFrame = function(){
    return (
        window.requestAnimationFrame       || 
        window.webkitRequestAnimationFrame || 
        window.mozRequestAnimationFrame    || 
        window.oRequestAnimationFrame      || 
        window.msRequestAnimationFrame     || 
        function(/* function */ callback){
            window.setTimeout(callback, 1000 / 60);
        }
    );
}();

JavaScript会在return语句后自动添加一个分号。它这样做是因为在return语句后面紧跟着一个换行符并且下一行是一个有效的表达式。实际上,它会被转化为:

return;
window.requestAnimationFrame       || 
window.webkitRequestAnimationFrame || 
window.mozRequestAnimationFrame    || 
window.oRequestAnimationFrame      || 
window.msRequestAnimationFrame     || 
function(/* function */ callback){
    window.setTimeout(callback, 1000 / 60);
};

这段代码返回undefined,并且永远不会执行return语句后面的代码。所以window.requestAnimFrameundefined。当你在animloop中调用它时,JavaScript会产生一个错误并停止执行。你可以通过将表达式括在括号中来解决这个问题。

建议使用Chrome开发者工具或Firebug来检查JavaScript执行。使用这些工具,你将看到错误。你应该按照以下步骤进行调试(我假设使用Chrome):

  1. 执行代码(产生意外结果)
  2. 打开开发者工具(右键单击->检查元素)。 你会在右侧状态栏看到一个红色的x(这意味着执行过程中出现了错误)
  3. 打开控制台选项卡
  4. 你将看到
    Uncaught TypeError:[object DOMWindow]对象的属性'requestAnimFrame'不是函数
  5. 在控制台中输入:window.requestAnimFrame,然后按回车键,你会发现它是undefined。现在你知道问题实际上与requestAnimationFrame无关,而应该集中精力解决代码的第一部分。
  6. 现在需要逐步缩小代码的范围,直到找到返回结果的那一部分。这是困难的部分,如果你在这个阶段仍然找不到症结,可能需要上网寻求更多帮助。

此外,观看这个视频,了解JavaScript编写的一些好习惯,他还提到了邪恶的自动分号插入。


在不支持requestAnimationFrame的浏览器中,使用此方法处理scrollresize等事件将会一直触发回调函数,而不是预期的效果,因为setTimeout会不断地被触发,创建新的计时器并一个接一个地运行它们。使用节流方法更加适合。另外,你不需要在每个全局变量前写上window,这是多余的。 - vsync
在编写代码时,在全局变量前加上“window”可以让其他开发人员清楚地知道这些可能是内置的,而不是本地作用域中提供的函数。考虑到现代JavaScript应用程序中bower/webpack等的复杂作用域,这是合理的。这并不会对任何事情造成伤害。 - Kyle Baker

8
 /*
  Provides requestAnimationFrame in a cross browser way.
  http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 */

if (!window.requestAnimationFrame) {

    window.requestAnimationFrame = (function() {

        return window.webkitRequestAnimationFrame ||
            window.mozRequestAnimationFrame || // comment out if FF4 is slow (it caps framerate at ~30fps: https://bugzilla.mozilla.org/show_bug.cgi?id=630127)
        window.oRequestAnimationFrame ||
            window.msRequestAnimationFrame ||
            function( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {

                window.setTimeout(callback, 1000 / 60);

        };

    })();

}

animate();

function animate() {
    requestAnimationFrame(animate);
    draw();
}

function draw() {
    // Put your code here
}

请查看下面的jsfiddle示例;它清楚地说明了我的意思;希望这能帮到您!http://jsfiddle.net/XQpzU/4358/light/

1
您在animate()内部的调用使用了错误的函数名称。Paul在他的示例中确实使用requestAnimFrame,但是您正在shimming requestAnimationFrame(顺便说一句,我更喜欢它)。很容易修复。 - Micros
感谢您的留言,Micros!我已经进行了编辑。我大多数时候喜欢使用像你一样命名较长的函数。我想这个习惯是从使用 obj-c 语言开始的 :) - Gokhan Tank

0
"智能节流,确保事件不会触发超过屏幕可以重绘更改的次数:"

var requestFrame = window.requestAnimationFrame ||
                   window.webkitRequestAnimationFrame ||
                   // polyfill - throttle fall-back for unsupported browsers
                   (function() {
                       var throttle = false,
                           FPS = 1000 / 60; // 60fps (in ms)
       
                       return function(CB) {
                         if( throttle ) return;
                         throttle = true;
                         setTimeout(function(){ throttle = false }, FPS);
                         CB(); // do your thing
                       }
                    })();

/////////////////////////////
// use case:

function doSomething() {
  console.log('fired');
}

window.onscroll = function() {
  requestFrame(doSomething);
};
html, body{ height:300%; }
body::before{ content:'scroll here'; position:fixed; font:2em Arial; }


我不确定,但FPS可能应该是1000/60,即60fps。 - Ampersanda
嘿,vsync,你知道如何使用requestAnimationFrame来平滑滚动页面吗? - Ampersanda
@Ampersanda - 以何种方式缓解?当用户使用鼠标滚轮进行手动滚动时? - vsync
1
@oldboy - 这是非常好的,而且绝对不会昂贵。 - vsync
函数的作用域与性能有什么关系?永远不会有性能损失,你为什么要为无谓的事情担心这么多呢?只需测试并查看即可。 - vsync
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接