用setTimeout对比理解requestAnimationFrame

1,417 阅读2分钟

在H5标准出现之前,实现网页动画的方式无非就是 setTimeoutsetInterval ,但这种方式弊端很多,最常见的就是占用资源高造成卡顿现象。H5和CSS3出现之后,便出现了很多动画的实现方式,如CSS3中的 transitionanimation ,H5中的 canvas,还有一个比较常见但却又是比较难理解的 requestAnimationFrame ,它的使用方法跟 setTimeoutsetInterval 比较接近,所以接下来我们就会通过对比 setTimeoutsetInterval 来认识 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();

效果:

settimeoutexample

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);

效果:

requestanimationframe

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行

引自MDN —— window.requestAnimationFrame

requestAnimationFrame 的使用方法跟 setTimeout 区别不大,但是主要的区别是 requestAnimationFrame 的执行时机是浏览器去决定的,比如当前设备刷新率是60Hz,那它的执行就会是16.67ms。也可以说它的回调函数的执行是与屏幕的刷新有关,每刷完一次,就要执行一次,不会卡顿和掉帧。由于它定义的回调是在下次重绘之前,所以必须在回调里再调用一次,才能实现连续的动画。而且 requestAnimationFrame 在页面隐藏掉或最小化的时候,是不会执行的,这样就节省了一些不必要的系统资源。所以使用 requestAnimationFramesetTimeout 好处要多。