• 使用 requestAnimationFrame 提升 web 性能


    requestAnimationFrame 与延时执行函数 setTimeout 类似,但不用设置时间,即可将操作延迟至下一次网页重绘时执行。

    requestAnimationFrame 的用法

    延迟到下一帧执行

    requestAnimationFrame(回调函数)
    
    • 1

    功能:将回调函数,延迟到下一帧(下一次网页重绘)前执行。

    每一帧都执行

    在回调函数内再次调用 requestAnimationFrame()

    requestAnimationFrame(function update() {
      console.log("执行了更新!");
      // requestAnimationFrame 的回调函数默认只会被调用一次,如果希望每帧都执行,则每帧都需要调用
      requestAnimationFrame(update);
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    取消执行

    requestAnimationFrame(回调函数) 会返回一个整数(定时器的编号),将其传给cancelAnimationFrame(定时器的编号) 可取消回调函数的执行,如:

    // 定义定时器
    let timer1 = requestAnimationFrame(回调函数)
    // 取消定时器
    cancelAnimationFrame(timer1)
    
    • 1
    • 2
    • 3
    • 4

    requestAnimationFrame 的常见应用场景

    提升 web 性能

    使用 requestAnimationFrame 替换 setTimeout 和 setInterva 可提升web性能。

    setTimeout 和 setInterva 实现动画的缺点

    • 时间不精确,通常实际执行时间都要比其设定的时间晚一些。
    • 实现的动画在某些低端机上会出现卡顿、抖动的现象。(不同设备的屏幕刷新频率不同,而 setTimeout 和 setInterva 只能设置一个固定的时间间隔,这个时间和屏幕的刷新时间不同时,就会出现掉帧,而导致页面卡顿、抖动)

    requestAnimationFrame 实现动画的优势

    requestAnimationFrame 采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。

    全局监听浏览器滚动事件

    // 全局监听浏览器滚动事件
    window.onscroll = function () {
      requestAnimationFrame(function scroll() {
        console.log("页面滚动了");
      });
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    平滑滚动

    <button onclick="scrollToTop()">回到顶部button>
    
    • 1
    function scrollToTop() {
        const c = document.documentElement.scrollTop || document.body.scrollTop;
        if (c > 0) {
            window.requestAnimationFrame(scrollToTop);
            window.scrollTo(0, c - c / 8);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    大量数据的渲染

    比如十万条数据的渲染,使用 requestAnimationFrame 分页加载。

    //需要插入的容器
    let ul = document.getElementById('container')
    // 插入十万条数据
    let total = 100000
    // 一次插入 20 条
    let once = 20
    //总页数
    let page = total / once
    //每条记录的索引
    let index = 0
    //循环加载数据
    function loop(curTotal, curIndex) {
      if (curTotal <= 0) {
        return false
      }
      //每页多少条
      let pageCount = Math.min(curTotal, once)
      window.requestAnimationFrame(function () {
        for (let i = 0; i < pageCount; i++) {
          let li = document.createElement('li')
          li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
          ul.appendChild(li)
        }
        loop(curTotal - pageCount, curIndex + pageCount)
      })
    }
    loop(total, index)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    使用 setTimeout 实现的写法如下:

    //需要插入的容器
    let ul = document.getElementById('container')
    // 插入十万条数据
    let total = 100000
    // 一次插入 20 条
    let once = 20
    //总页数
    let page = total / once
    //每条记录的索引
    let index = 0
    //循环加载数据
    function loop(curTotal, curIndex) { 
      if (curTotal <= 0) {  
        return false 
      }  
      //每页多少条
      let pageCount = Math.min(curTotal, once) 
      setTimeout(() => {  
        for (let i = 0; i < pageCount; i++) { 
          let li = document.createElement('li')    
          li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)    
          ul.appendChild(li)  
        }  
        loop(curTotal - pageCount, curIndex + pageCount) 
      }, 0)
    }
    loop(total, index)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    监控页面的卡顿

    比如连续出现 3 个低于 20 的 FPS 即可认为网页存在卡顿。

    var lastTime = performance.now()
    var frame = 0
    var lastFameTime = performance.now()
    var loop = function (time) {
      var now = performance.now()
      var fs = now - lastFameTime
      lastFameTime = now
      var fps = Math.round(1000 / fs)
      frame++
      if (now > 1000 + lastTime) {
        var fps = Math.round((frame * 1000) / (now - lastTime))
        frame = 0
        lastTime = now
      }
      window.requestAnimationFrame(loop)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    requestAnimationFrame 的优点

    • requestAnimationFrame 会把每一帧中所有的DOM操作集中起来,在一次重绘/回流中完成。

    重绘:当某个元素颜色样式发生更改时(如背景颜色、文字颜色),页面也需要更新,浏览器需要重新绘制元素,称为重绘(repaint)。

    回流:当页面上的某一个元素的大小或者位置发生更改时,都会影响到与它相邻元素的状况,甚至整个页面的元素状态(位置、元素大小)都需要重新计算和更新。这种操作称为回流(reflow)或者布局(layout)。一个页面至少会有一次回流,就是在页面初始化时。

    • requestAnimationFrame 重绘/回流的时间间隔紧紧跟随浏览器的刷新频率。充分利用了显示器的刷新机制,比较节省系统资源(显示器有固定的刷新频率60Hz/75Hz,即每秒最多重绘60次或75次,requestAnimationFrame的执行紧跟着系统的绘制频率走,便能保证回调函数在屏幕每一次的绘制间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题)。
    • 对隐藏或不可见的元素,requestAnimationFrame不会进行重绘/回流,这意味着更少的CPU、GPU和内存使用量。
    • 当页面被隐藏或最小化时,requestAnimationFrame会停止渲染(setTimeout 和setInterval 仍然在后台执行动画任务),当页面被激活时,动画会从上次停留的地方继续执行,有效节省了CPU、GPU和电力开销。
    • requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用

    requestAnimationFrame 的缺点

    • requestAnimationFrame 存在兼容性问题,在不支持的浏览器(如 IE9),需添加兼容代码
    • requestAnimationFrame 使用的主线程,若主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。

    兼容写法

    对不支持 requestAnimationFrame 的浏览器(如 IE9),需在代码前添加以下兼容代码:

    if(!window.requestAnimationFrame){
        var lastTime = 0;
        window.requestAnimationFrame = function(callback){
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0,16.7-(currTime - lastTime));
            var id  = window.setTimeout(function(){
                callback(currTime + timeToCall);
            },timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
  • 相关阅读:
    微信小程序2022年发展方向曝光
    mybatis
    APS高级排产助力电子元器件企业盈利质量提升
    OpenCV实现手势音量控制
    CentOS下多网卡绑定多IP段时导致只有一个会通的问题解决
    java中的String类[36]
    从零开始的C语言学习第二十课:数据在内存中的存储
    2023松山湖软件和信息服务业高质量发展大会顺利举行
    Java正则表达式正确,find()方法返回false,可能是因为\s无法匹配空格
    Java面向对象编程
  • 原文地址:https://blog.csdn.net/weixin_41192489/article/details/126247756