requestAnimationFrame思考
关于setTimeout
首先要明白,setTimeout 的执行只是在内存中对元素属性进行改变,这个变化必须要等到屏幕下次绘制时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的元素。假设屏幕每隔16.7ms刷新一次,而setTimeout 每隔10ms设置图像向左移动1px, 就会出现如下绘制过程(表格):
- 第 0 ms:屏幕未绘制, 等待中,setTimeout 也未执行,等待中;
- 第 10 ms:屏幕未绘制,等待中,setTimeout 开始执行并设置元素属性 left=1px;
- 第 16.7 ms:屏幕开始绘制,屏幕上的元素向左移动了 1px, setTimeout 未执行,继续等待中;
- 第 20 ms:屏幕未绘制,等待中,setTimeout 开始执行并设置 left=2px;
- 第 30 ms:屏幕未绘制,等待中,setTimeout 开始执行并设置 left=3px;
- 第33.4 ms:屏幕开始绘制,屏幕上的元素向左移动了 3px, setTimeout 未执行,继续等待中;
…
从上面的绘制过程中可以看出,屏幕没有更新 left=2px 的那一帧画面,元素直接从left=1px 的位置跳到了 left=3px 的的位置,这就是丢帧现象,这种现象就会引起动画卡顿。
关于requestAnimationFrame
与 setTimeout 相比,rAF 最大的优势是 由系统来决定回调函数的执行时机。具体一点讲就是,系统每次绘制之前会主动调用 rAF 中的回调函数
,如果系统绘制率是 60Hz,那么回调函数就每16.7ms 被执行一次,如果绘制频率是75Hz,那么这个间隔时间就变成了 1000/75=13.3ms。换句话说就是,rAF 的执行步伐跟着系统的绘制频率走。它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次
,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
但是rAF并不能保证每次绘制都会执行,如果有个计算任务执行了 20ms,那么再一次回调会在 16.7 * 3 ms时执行,跳过了一次绘制。16.7+20 = 36.7,36.7+16.7 = 53.4, 16.7 * 3 = 50.1, 50.1<53.4
rAF的优势
CPU节能:使用 setTimeout 实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,而且还浪费 CPU 资源。而 rAF 则完全不同,当页面处理未激活的状态下,该页面的屏幕绘制任务也会被系统暂停,因此跟着系统步伐走的 rAF 也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了 CPU 开销。像是埋点也可以使用这个特性,当页面不可见时,不需要上报埋点,等页面可见时再上报。
函数节流:在高频率事件(resize,scroll 等)中,为了防止在一个刷新间隔内发生多次函数执行,使用 rAF 可保证每个绘制间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个绘制间隔内函数执行多次时没有意义的,因为显示器每16.7ms 绘制一次,多次绘制并不会在屏幕上体现出来。