Skip to content

webAPI-requestAnimationFrame #195

Open
@yaofly2012

Description

@yaofly2012

一、背景(让JS动画更丝滑)

The number of callbacks is usually 60 times per second
refresh rate
执行时机

repaint

1.1 流畅动画的标准

理论上说,FPS 越高,动画会越流畅,目前大多数设备的屏幕刷新率为 60 次/秒,所以通常来讲 FPS 为 60 frame/s 时动画效果最好,也就是每帧的消耗时间为 16.67ms。

1.2 代替setTimeout/setInterval实现JS动画

尽管setTimeout/setInterval这两个API可以实现JS动画,但是丢帧的风险很高。因为回调函数会在帧中的某个时间点执行,也许是在最后面,会导致单个或多帧丢失。

二、语法

2.1 基础

id = requestAnimationFrame(callback)

1. callback只是个回调函数,不是事件处理函数。其实参是个DOMHighResTimeStamp,不是事件对象;

requestAnimationFrameId = requestAnimationFrame((timestamp) => {                        
  console.log(`rAF callback ${timestamp}, performance.now=${performance.now()}`)
})

这里的Demo中timestamp要小于performance.now()的返回值。因为前者赋值时发生的早,于callback执行。

2. 调用频率

回调函数执行次数通常是每秒60次(递归调用情况下),但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。即刷新频率越高,执行次数越多。
注意requestAnimationFrame并不像setInterval那样一次调用一直调用,requestAnimationFrame被调用一次,则callback执行一次(callback更多的是叫tick)。

3. 批调用

多次调用requestAnimationFrame注册的多个callback会在下次repaint前统一调用:

  • 所有callback的实参是一样的(即触发时间点一致);
  • 执行顺序是注册顺序。

2.2 什么时候执行?【TODO 了解浏览器的渲染机制】

浏览器执行下次repaint前。

2.3 callback耗时多久最好?

跟其他函数一样, callback如果耗时比较久的话会影响浏览器渲染的,会出现卡顿(浏览器FPS会下降)。为了不影响浏览器渲染还行时间应该小于16.7ms。

2.4 监测应用卡顿

利用requestAnimationFrame(callback)每秒60次调用频率计算浏览器的FPS。

                var lastCallTime;
                var count= 0;

                function loop(timestamp) {      
                    if(timestamp == null ) {
                        lastCallTime = performance.now();
                    } else {
                        var timeSpan = timestamp - lastCallTime;

                        ++count;
                        if(timeSpan > 1000) {                            
                            console.log(`count: ${count}`);
                            lastCallTime = timestamp;
                            count = 0;
                        }  
                    }                            

                    requestAnimationFrame(loop)
                }
  1. 计算1s内callback调用次数。

2.5 优点

  1. 批调用

The browser can optimize concurrent animations together into a single reflow and repaint cycle, leading to higher fidelity animation.

即多次调用requestAnimationFrame注册的回调函数,在下次repain前同步一次调用(实现了批调用)
2. 切到后台tab或者隐藏iframe时,requestAnimationFrame回调函数会停止触发。减少资源(CPU,GPU,内存等)占用。
3. 更省电(因为减少资源使用)

2.6 requestAnimationFrame == throttle(func, 16) ?

requestAnimationFrame有点类似对JS动画处理函数的节流操作。

三、polyfill

Tino Zijdel:paulirish/rAF.js

function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() {
                    // callback的实参不是实时利用`new Date().getTime()`获取的,而是个理想的预估时间
                    callback(currTime + timeToCall); 
                }, timeToCall);
                
            // lastTime为啥不是在setTimeout回调函数里更新,而是在这里同步更新
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());
  1. 各种厂商前缀,利用for循环不错,省得对requestAnimationFramecancelAnimationFrame分别进行||判断;
  2. webkitCancelRequestAnimationFrame是个特例, Chrome 16 has webkitCancelRequestAnimationFrame
  3. 利用setTimeout做兜底
  • 逻辑就像是个throttle(callback, 16)setTimeout的delay时间是动态计算的,不是写死的16ms,这样对于执行间隔大于16ms的会立马执行,而小于16ms的则动态计算还需要等待的时间。
  1. 上面polyfill有个问题
  • FireFox有些版本只存在raf,但是不存在caf。所以应该只要两者任意不存在,就要启动setTimeout 兜底。
  • callback的参数计算利用的Date.now时间戳,不符合API规则。
    应该使用performance.now()
  • 每次调用都会产生一个新的setTimeout(假如走到兜底逻辑了),并没有实现原始API聚合多个处理函数逻辑的功能。

Tino Zijdel:paulirish/rAF.js为基础的npm库raf实现的polyfill更符合好些(解决了上诉polyfill的不足)。

四、总结:

作为代替setTimeout/setInterval实现JS动画的解决方案

  1. 规定好调用时间点,不掉帧。并且进行的是批调用,有利于性能优化。
  2. 切到后台的tab或者隐藏的iframe会暂停callback调用,节省资源。

参考

  1. MDN: requestAnimationFrame
  2. html5rocks: Leaner, Meaner, Faster Animations with requestAnimationFrame
  3. requestAnimationFrame for Smart Animating
  4. Debouncing and Throttling Explained Through Examples

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions