requestAnimationFrame和requestIdleCallback

简介

requestAnimationFrame和requestIdleCallback的应用场景并不一样,requestAnimationFrame是为了实现更流畅和性能更好的动画;后者是为了在渲染空闲时间执行优先级不高的操作,以避免阻塞渲染。
两者放到一起进行说明是因为它们都是由浏览器控制执行时机,而不是由开发者通过定时器控制。另外,相对于不使用这两个方法,使用它们都能在一定的情况下获得性能的提升。

在了解这两个方法之前要先回顾一下渲染流水线。

在性能优化中已经讨论过渲染流水线。那么这两个方法的回调在渲染流水线中的什么位置呢?

用户输入事件,包括敲击键盘、点击、滚动等;还有js执行(可能修改样式),都可能触发重新渲染。

渲染完成后,在下一帧绘制之前如果有requestAnimationFrame,则调用之。如果在一帧渲染完后有空闲,就会执行requestIdleCallback注册的回调。什么算是空闲呢?这和浏览器渲染的频率有关系。一般情况下浏览器渲染的频率会和显示器保持一致。通常1s 60帧的帧率可以让肉眼感觉不卡顿。1s ÷ 60 ≈ 16ms。我们以60fps为例,如果浏览器渲染一帧发现不到16ms,那么剩余时间就算是空闲时间。

渲染过程

requestAnimationFrame

通常我们可以通过定时器(setTimeout/setInterval)实现一个动画,但是定时器时间并不精确,如果时间太短,那么可能造成多余的操作,消耗CPU,如果时间长,就会导致动画不流畅。另外当画面不展示时候,定时器依然执行,导致不必要的CPU资源消耗,耗电更快。

requestAnimationFrame实现动画可以解决上面两个问题。

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

使用:

接受一个回调,在下次渲染前执行

该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同,它表示requestAnimationFrame() 开始去执行回调函数的时刻,这个时间用来进行动画参数的计算。

参考官方示例

const element = document.getElementById('some-element-you-want-to-animate');
let start;

function step(timestamp) {
  if (start === undefined)
    start = timestamp;
  const elapsed = timestamp - start;

  //这里使用`Math.min()`确保元素刚好停在200px的位置。
  element.style.transform = 'translateX(' + Math.min(0.1 * elapsed, 200) + 'px)';

  if (elapsed < 2000) { // 在两秒后停止动画
    window.requestAnimationFrame(step);
  }
}

window.requestAnimationFrame(step);

由于该方法只是通知浏览器下一次绘制之前执行一次callback,因此实现动画时候需要每次重新调用。

requestAnimationFrame返回一个id,取消回调的方法是cancelAnimationFrame,请看官方示例

var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
                            window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;

var start = window.mozAnimationStartTime;  // 只有Firefox支持mozAnimationStartTime属性,其他浏览器可以使用Date.now()来替代.

var myReq;
function step(timestamp) {
  var progress = timestamp - start;
  d.style.left = Math.min(progress/10, 200) + "px";
  if (progress < 2000) {
    myReq = requestAnimationFrame(step);
  }
}
myReq = requestAnimationFrame(step);

window.cancelAnimationFrame(myReq);

需要注意动画中元素样式最好根据时间进行计算,而不是使用需要样式计算的DOM API(例如ele.style.offsetWidth),否则会强制重新渲染。

requestIdleCallback

接受一个回调,回调在浏览器空闲时间执行。注意如果浏览器一直渲染没有空闲,可能就一直执行不到requestIdelCallback注册的回调,因此可以设置一个超时时间,超时之后会执行注册的回调。

取消回调的方法是 cancelIdleCallback

var handle = window.requestIdleCallback(callback, {timeout: 1000});

cancelIdleCallback(handle);

注意,requestIdleCallback用来执行优先级低的任务,由于其执行时机不可控,因此尽量不要执行DOM操作。

参考文章

灵题库-前端题库

requestIdleCallback和requestAnimationFrame详解

网页渲染性能优化

被誉为神器的requestAnimationFrame

浏览器帧原理剖析

你可能感兴趣的:(requestAnimationFrame和requestIdleCallback)