requestAnimationFrame
window.requestAnimationFrame()
方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。
⚠️ 注意:若您想要在下一次重绘时产生另一个动画画面,您的回调例程必须调用
requestAnimationFrame()
。
传统动画渲染的弊端
传统的动画渲染是通过 setTimeout 和 setInterval 进行实现,但是这两种定时器会有两个弊端:
- 动画的时间间隔不好确定,设置时间过长会使得动画不够平滑流畅,设置过短会令浏览器的重绘频率容易达到瓶颈(推荐最佳循环间隔是 17ms,因为大多数电脑的显示器刷新频率是 60Hz,1000ms/60)。
- 定时器的第二个时间参数只是指定了多久后将动画任务添加到浏览器的 UI 线程队列中,如果 UI 线程处于忙碌状态,那么动画不会立即执行。
语法
requestAnimationFrame
window.requestAnimationFrame(callback);
参数 | 说明 | 类型 |
---|---|---|
callback | 下次重新绘制动画时调用的回调函数。该回调函数只有一个参数 DOMHighResTimeStamp ,指示 requestAnimationFrame() 开始出发回调函数的当前时间。 | function |
返回值 | 类型 |
---|---|
请求动画渲染的标识 ID。是个非零值,没有其他意义。可用作 window.cancelAnimationFrame() 以取消回调函数。 | number 整数 |
cancelAnimationFrame
window.cancelAnimationFrame(requestID);
参数 | 说明 | 类型 |
---|---|---|
requestId | 指定动画渲染的标识符 | number |
优点
requestAnimationFrame
会把每一帧中的所有 DOM 操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率- 在隐藏或不可见的元素中,或者浏览器标签页不可见时,
requestAnimationFrame
将不会进行重绘或回流,这当然就意味着更少的 CPU、GPU 和内存使用量 requestAnimationFrame
是由浏览器专门为当年规划提供的 API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了 CPU 开销
Firefox
、Chrome
、IE10+
对 requestAnimationFrame
支持很好,但不兼容 IE9-
浏览器,但是我们可以用定时器完成兼容性改造。
(function () {var lastTime = 0;var vendors = ['webkit', 'moz'];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) {/*调整时间,让一次动画等待和执行时间在最佳循环时间间隔内完成*/var currTime = new Date().getTime();var timeToCall = Math.max(0, 17 - (currTime - lastTime));var id = window.setTimeout(function () {callback(currTime + timeToCall);}, timeToCall);lastTime = currTime + timeToCall;return id;};if (!window.cancelAnimationFrame)window.cancelAnimationFrame = function (id) {clearTimeout(id);};
})();
传递参数
function requestAnimation(a, b, c) {if () {window.requestAnimationFrame(function () {requestAnimation(a, b, c)})}
}
requestIdleCallback
一般浏览器的刷新率为 60HZ,即 1 秒钟刷新 60 次。1000ms / 60hz = 16.6
,大概每过 16.6ms 浏览器会渲染一帧画面。
在这段时间内,浏览器大体会做两件事:task 与 render。
task -> requestAnimationFrame -> render -> requestIdleCallback
如果渲染完成后还有空闲时间,则 requestIdleCallback API 会被调用。
掉帧与时间切片
如果 task
执行时间超过了 16.6ms(比如 task 中有个很耗时的 while
循环)。
那么这一帧就没有时间 render
,页面直到下一帧 render
后才会更新。表现为页面卡顿一帧,或者说掉帧。
最好的办法是时间切片,把长时间 task 分割为几个短时间 task。
为了解决掉帧造成的卡顿,React16 将递归的构建方式改为可中断的遍历。React16 就是基于 requestIdleCallbackAPI,实现了自己的 Fiber Reconciler。
以 5ms
的执行时间划分 task,每遍历完一个节点,就检查当前 task 是否已经执行了 5ms
。
如果超过 5ms
,则中断本次 task
。
通过将 task
执行时间切分为一个个小段,减少长时间task
造成无法 render
的情况,这就是时间切片。