一、早期动画循环的缺点
早期创建动画循环的方式是使用setInterval()方法来控制所有动画:
(function(){
setInterval(() => {
console.log("animation...");
}, 1000/60);
})();
编写这种动画循环的关键是要知道延迟时间多长合适:
- 一方面,循环间隔必须足够短,这样才能让不同的动画效果显得更平滑流畅。
- 另一方面,循环间隔还要足够长,这样才能确保浏览器有能力渲染产生的变化。
大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。
因此,最平滑动画的最佳循环间隔是1000ms/60,约等于17ms。以这个循环间隔重绘的动画是最平滑的,因为这个速度最接近浏览器的最高限速。
但是,无论setInterval()还是setTimeout()都不十分精确。为它们传入的第二个参数,实际上只是指定了动画代码添加到浏览器UI线程队列中以等待执行的时间。如果UI线程繁忙,比如忙于处理用户操作,那么即使把代码加入队列也不会立即执行。
二、requestAnimationFrame()
requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
而且为了提高性能和电池寿命,在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。
回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。
示例:
var startTime = Date.now();
(function animation(timestamp) {
let diff = timestamp - startTime;
startTime = timestamp;
console.log(diff);
requestAnimationFrame(animation);
})();
可以发现,循环间隔几乎都是16ms左右: