阅读 234
requestAnimationFrame

requestAnimationFrame

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战 

目录

  1. 浏览器渲染过程
  2. requestAnimationFrame
  3. 应用案例:进度条&文字跑马灯
  4. 浏览器兼容性

1. 浏览器渲染过程

浏览器通过http请求获取到资源之后,会将HTML解析成一棵DOM树,并根据CSS构建一棵CSSOM树,然后将DOM树和CSSOM树结合,组合成一棵Render树;再根据Render树进行布局(layout),即确定每个树节点的宽、高、位置及元素之间的位置关系(第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为重排(reflow),也有些文章称之为回流。)。布局完成后,浏览器就可以在屏幕上进行像素绘制

浏览器渲染过程

当Render树中的节点样式发生改变时,如果是节点的宽、高或位置等,会引起布局改变的变化,那么就需要进行重排,重排之后必然引起重绘(注:重绘不一定会引起重排)。

大部分浏览器在一秒钟内可以显示的最大帧数是60(刷新率FPS=60Hz)。也就是说,无论你每秒渲染多少帧,一秒钟内最多只有 60 帧会出现在屏幕上。两帧之间的最大间隔时长是1000/60ms,如下图所示,浏览器会在两次刷新间隔里占用部分或全部时间去做渲染。

图源:https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Rendering/frames-and-refresh-rate.svg

当浏览器渲染某一帧的时长超过了1000/60ms会发生什么?

图源:https://developer.mozilla.org/en-US/docs/Web/API/WebXR_Device_API/Rendering/dropped-frames-timing.svg

如上图所示,因为第二帧Frame2的时长超过了1000/60ms,而挤占了原本第三帧Frame3的时间,而浏览器也不会将第三帧延迟渲染,而是直接跳过第三帧,继续渲染第四帧Frame4。这就是所谓的“掉帧”。

2. requestAnimationFrame

以往的JS动画通常是用setTimeout或setInterval定时器来实现,这种实现方法存在的问题,一是上面提到的“掉帧”,二是无法掌握回调函数的执行时机,三是系统性能的浪费,当页面转为后台运行时并不会自动停止。

requestAnimationFrame则解决了定时器的这些问题。当然,requestAnimationFrame并非完美,因为是在主线程上执行的,当主线程非常繁忙时,requestAnimationFrame的效果就大打折扣。

3. 应用案例

3.1 进度条

先看下效果图:

进度条.gif

核心代码:

 function stepper() {
    window.requestAnimationFrame(() => {
      if (timer <= max) {
        // 进度条长度设置
        progresser.style.width = timer + "%";
        // 展示百分比数值
        label.innerText = timer + "%";
        // 更新数值文本的位置
        label.style.left =
          timer - (label.offsetWidth / bar.offsetWidth) * 100.0 + "%";
        timer++;
        // 当进度条未达max值,则下一帧继续渲染动画
        stepper();
      }
    });
}
复制代码

完整代码在线调试:jsrun.net/zx8Kp/edit

3.2 文字跑马灯

效果图:

核心代码:

 function step() {
    // 当文字位移小于容器长度,继续向右移动
    if (left <= length) {
      text.style.left = left + "px";
      text.style.width = width + "px";
      left += move;
    } else {
    // 当文字位移超出容器长度,重新从左边开始移动
      left = -width;
      text.style.left = left + "px";
      text.style.width = width + "px";
    }
    // 无限循环跑马灯动画
    window.requestAnimationFrame(step);
}
复制代码

完整代码在线调试:jsrun.net/Px8Kp/edit

4. 浏览器兼容性

目前大部分浏览器都已实现window.requestAnimationFrame:

图源:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

对于未实现的window.requestAnimationFrame的浏览器也有开源的polyfill可供使用,如rAF.js(gist.github.com/paulirish/1…),代码不到30行:

(function() {
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    }
 
    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) {
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        };
 
    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) {
            clearTimeout(id);
        };
}());
复制代码

参考资料:

[1]渲染页面-浏览器工作原理:developer.mozilla.org/zh-CN/docs/…

[2]window.requestAnimationFrame:developer.mozilla.org/zh-CN/docs/…

[3]rAF.js:gist.github.com/paulirish/1…

文章分类
前端
文章标签