requestAnimationFrame和requestIdleCallback的应用场景并不一样,requestAnimationFrame是为了实现更流畅和性能更好的动画;后者是为了在渲染空闲时间执行优先级不高的操作,以避免阻塞渲染。
两者放到一起进行说明是因为它们都是由浏览器控制执行时机,而不是由开发者通过定时器控制。另外,相对于不使用这两个方法,使用它们都能在一定的情况下获得性能的提升。
在了解这两个方法之前要先回顾一下渲染流水线。
在性能优化中已经讨论过渲染流水线。那么这两个方法的回调在渲染流水线中的什么位置呢?
用户输入事件,包括敲击键盘、点击、滚动等;还有js执行(可能修改样式),都可能触发重新渲染。
渲染完成后,在下一帧绘制之前如果有requestAnimationFrame,则调用之。如果在一帧渲染完后有空闲,就会执行requestIdleCallback注册的回调。什么算是空闲呢?这和浏览器渲染的频率有关系。一般情况下浏览器渲染的频率会和显示器保持一致。通常1s 60帧的帧率可以让肉眼感觉不卡顿。1s ÷ 60 ≈ 16ms。我们以60fps为例,如果浏览器渲染一帧发现不到16ms,那么剩余时间就算是空闲时间。
渲染过程
通常我们可以通过定时器(setTimeout/setInterval)实现一个动画,但是定时器时间并不精确,如果时间太短,那么可能造成多余的操作,消耗CPU,如果时间长,就会导致动画不流畅。另外当画面不展示时候,定时器依然执行,导致不必要的CPU资源消耗,耗电更快。
requestAnimationFrame实现动画可以解决上面两个问题。
window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
使用:
接受一个回调,在下次渲染前执行
该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻,这个时间用来进行动画参数的计算。
参考官方示例
const element = document.getElementById('some-element-you-want-to-animate');
let start;
function step(timestamp) {
if (start === undefined)
start = timestamp;
const elapsed = timestamp - start;
//这里使用`Math.min()`确保元素刚好停在200px的位置。
element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';
if (elapsed < 2000) { // 在两秒后停止动画
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
由于该方法只是通知浏览器下一次绘制之前执行一次callback,因此实现动画时候需要每次重新调用。
requestAnimationFrame返回一个id,取消回调的方法是cancelAnimationFrame,请看官方示例
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
var start = window.mozAnimationStartTime; // 只有Firefox支持mozAnimationStartTime属性,其他浏览器可以使用Date.now()来替代.
var myReq;
function step(timestamp) {
var progress = timestamp - start;
d.style.left = Math.min(progress/10, 200) + "px";
if (progress < 2000) {
myReq = requestAnimationFrame(step);
}
}
myReq = requestAnimationFrame(step);
window.cancelAnimationFrame(myReq);
需要注意动画中元素样式最好根据时间进行计算,而不是使用需要样式计算的DOM API(例如ele.style.offsetWidth),否则会强制重新渲染。
接受一个回调,回调在浏览器空闲时间执行。注意如果浏览器一直渲染没有空闲,可能就一直执行不到requestIdelCallback注册的回调,因此可以设置一个超时时间,超时之后会执行注册的回调。
取消回调的方法是 cancelIdleCallback
var handle = window.requestIdleCallback(callback, {timeout: 1000});
cancelIdleCallback(handle);
注意,requestIdleCallback用来执行优先级低的任务,由于其执行时机不可控,因此尽量不要执行DOM操作。
灵题库-前端题库
requestIdleCallback和requestAnimationFrame详解
网页渲染性能优化
被誉为神器的requestAnimationFrame
浏览器帧原理剖析