在H5标准出现之前,实现网页动画的方式无非就是 setTimeout 和 setInterval ,但这种方式弊端很多,最常见的就是占用资源高造成卡顿现象。H5和CSS3出现之后,便出现了很多动画的实现方式,如CSS3中的 transition 和 animation ,H5中的 canvas,还有一个比较常见但却又是比较难理解的 requestAnimationFrame ,它的使用方法跟 setTimeout 和 setInterval 比较接近,所以接下来我们就会通过对比 setTimeout 和 setInterval 来认识 requestAnimationFrame。
setTimeout
先上一段代码:
HTML:
<style>
.block{
width: 100px;
height: 100px;
background-color: cyan;
}
</style>
<body>
<div class="block"></div>
<script>
var element = document.querySelector('.block');
element.style.position = 'absolute';
</script>
<script src="./index.js"></script>
</body>
JavaScript:
let left = 1;
function step(){
element.style.left = `${left++}px`;
let timer = setTimeout(() => {
step();
}, 1000/60);
if( timer && left > 100){
clearTimeout(timer)
}
}
step();
效果:
setTimeout 的方式实现动画是不难理解的,首先JS主线程中执行一次 step() 函数,step() 函数每次执行都会让block元素往左移动一像素,接着在主线程执行队列中如果没有其他任务,则在 1000/60ms 后再次执行 step() 函数,直到达到100像素。之所以用 1000/60 是因为浏览器的刷新率是每秒钟60Hz,也就是1000毫秒内刷新60次,所以需要(1000/60ms)16.67ms执行一次动画才能达到比较流畅的效果。看到这里,似乎没有理由使用除了 setTimeout 之外的东西实现动画了,因为它表现的的确很流畅。但是,这是在理想的情况下,如果在 EventLoop 执行队列执行 setTimeout 回调函数之前有其他任务等待执行(现实的Web应用往往存在大量的任务在执行队列等待执行),那么 setTimeout 回调就会延迟执行,直到执行队列轮到 setTimeout 回调,所以谁也不能保证 setTimeout 回调一定是在16.67ms后执行的。这样就会导致动画不流畅,甚至卡顿的现象发生,所以有必要引入一个新的专门用于动画的API,他就是 requestAnimationFrame
requestAnimationFrame
let left = 1;
function step() {
element.style.left = `${left}px`;
left *= 1.02;
if (left < 200) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
效果:
window.requestAnimationFrame()告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行
requestAnimationFrame 的使用方法跟 setTimeout 区别不大,但是主要的区别是 requestAnimationFrame 的执行时机是浏览器去决定的,比如当前设备刷新率是60Hz,那它的执行就会是16.67ms。也可以说它的回调函数的执行是与屏幕的刷新有关,每刷完一次,就要执行一次,不会卡顿和掉帧。由于它定义的回调是在下次重绘之前,所以必须在回调里再调用一次,才能实现连续的动画。而且 requestAnimationFrame 在页面隐藏掉或最小化的时候,是不会执行的,这样就节省了一些不必要的系统资源。所以使用 requestAnimationFrame 比 setTimeout 好处要多。