这种定时动画的问题在于,无法准确知晓循环之间的延时。
无论是 setInterval()
还是 setTimeout()
,都是不能保证时间精度的。作为第二个参数的延时只能保证何时会把代码添加到浏览器的任务队列,并不能保证添加到队列就会立即执行。如果队列前面还有其他任务,那么就要等这些任务执行完再执行。
简单来讲,这里的毫秒延时不是指何时这些代执行,而是指到时候会把回调添加到任务队列。如果添加到队列后,主线程还被其他任务占用,那么回调就不会立即执行。
知道何时绘制下一帧是创造平滑动画的关键,所以 setInterval()
和 setTimeout()
的不精确是个大问题。
浏览器自身计时器的精度让这个问题雪上加霜。浏览器计时器的精度不足毫秒,最厉害的 Chrome 计时器精度为 4ms。更麻烦的是,浏览器又开始对切换到后台或不活跃的标签页中的计时器执行限流,因此即使将时间间隔设为最优,也免不了只能得到近似的结果。
屏幕刷新率
一般计算机显示器的屏幕刷新率都是 60HZ,基本上意味着每秒需要重绘 60 次。大多数浏览器会限制重绘频率,使其不超过屏幕的刷新率,因为超过屏幕刷新率用户也感知不到。
所以,实现平滑动画最佳的重绘时间间隔为 1000ms/60,大约 17 ms。以这个速度重绘可以实现最平滑的动画,因为这已经是浏览器的极限了。
requestAnimationFrame
requestAnimationFrame()
这个方法可以通知浏览器某些 JavaScript 代码要执行动画了,这样浏览器就可以在运行某些代码后进行适当的优化。
requestAnimationFrame()
这个方法接收一个参数,该参数是一个要在重绘屏幕前调用的函数。这个函数就是修改 DOM 样式以反映下一次重绘有什么变化的地方。为了实现动画循环,可以把多个 requestAnimationFrame()
调用串联起来,就像以前使用 setTimeout()
一样: