requestAnimationFrame
很长时间以来,计时器和定时执行都是JavaScript 动画最先进的工具。虽然CSS 过渡和动画方便了 Web 开发者实现某些动画,但JavaScript 动画领域多年来进展甚微
早期定时动画
以前,在JavaScript 中创建动画基本上就是使用 setInterval() 来控制动画的执行。
(function() {
function updateAnimations() {
doAnimation1();
doAnimation2();
// 其他任务
}
setInterval(updateAnimations, 100);
})();
这个 updateAnimations()方法会周期性运行注册的动画任务,并反映出每个任务的变化(例如,同时更新滚动新闻和进度条)。
这种定时动画的问题在于无法准确知晓循环之间的延时。一般计算机显示器的屏幕刷新率都是 60Hz,基本上意味着每秒需要重绘 60 次。因此,实现平滑动画最佳的重绘间隔为 1000 毫秒/60,大约 17 毫秒。
但无论 setInterval() 还是 setTimeout() 都是不能保证时间精度的。作为第二个参数的延时只能保证何时会把代码添加到浏览器的任务队列,不能保证添加到队列就会立即运行。如果队列前面还有其他任务,那么就要等这些任务执行完再执行。简单来讲,这里毫秒延时并不是说何时这些代码会执行,而只是说到时候会把回调加到任务队列。如果添加到队列后,主线程还被其他任务占用,比如正在处理用户操作,那么回调就不会马上执行。
举个例子:
let startTime = new Date().getTime();
setTimeout(()=>{
let endTime = new Date().getTime();
console.log(endTime - startTime);
},50)
for(let i=0;i<20000;i++) {
console.log(1);
}
输出如下:
可以看到,设置了50毫秒后执行,实际执行延迟时间远大于这个数值,这就会导致动画效果并不会达到想要的效果。
requestAnimationFrame的出现
浏览器知道 CSS 过渡和动画应该什么时候开始,并据此计算出正确的时间间隔,到时间就去刷新用户界面。但对于JavaScript动画,浏览器不知道动画什么时候开始。
requestAnimationFrame() 方法,用以通知浏览器某些 JavaScript 代码要执行动画了。这样浏览器就可以在运行某些代码后进行适当的优化。
requestAnimationFrame() 方法接收一个参数,此参数是一个要在重绘屏幕前调用的函数。这个函数就是修改 DOM 样式以反映下一次重绘有什么变化的地方。这个函数仅执行一帧动画的渲染,并根据条件判断是否结束,如果动画没有结束,则继续调用requestAnimationFrame并将自身作为参数传入。这样就巧妙地避开了每一帧动画渲染的时间间隔问题。
function updateProgress() {
var div = document.getElementById("status");
div.style.width = (parseInt(div.style.width, 10) + 5) + "%";
if (div.style.left != "100%") {
requestAnimationFrame(updateProgress);
}
}
requestAnimationFrame(updateProgress);
因为 requestAnimationFrame() 只会调用一次传入的函数,所以每次更新用户界面时需要再手动调用它一次。同样,也需要控制动画何时停止。结果就会得到非常平滑的动画。
cancelAnimationFrame
与 setTimeout() 类似,requestAnimationFrame()也返回一个请求 ID,可以用于通过另一个方法 cancelAnimationFrame() 来取消重绘任务。下面的例子展示了刚把一个任务加入队列又立即将其取消:
let requestID = window.requestAnimationFrame(() => {
console.log('Repaint!');
});
window.cancelAnimationFrame(requestID);