美文网首页
你有用过requestAnimationFrame吗?

你有用过requestAnimationFrame吗?

作者: 白羊座的泰迪 | 来源:发表于2022-08-24 19:17 被阅读0次

    前言

    在自己练习动画逻辑的时候,到线上看别人写的实例,发现大家都在用requestAnimationFrame方法来处理下一帧的渲染,我因此也产生疑问,requestAnimationFrame到底是什么?他的优势在哪?

    一、API介绍

    1、什么是requestAnimationFrame

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

    简单来说,requestAnimationFrame 就是约定在下一次浏览器刷新前执行的一个定时器。类似setTimeout

    2、语法

      var ID = requestAnimationFrame(callback);
    
    • 参数:callback是requestAnimationFrame的回调函数。该回调函数会被传入DOMHighResTimeStamp参数,该参数与performance.now()的返回值相同。浏览器下一次刷新时就会执行这个回调函数。
    • 返回值:ID是requestAnimationFrame的调用时的返回值,是其在回调列表中的唯一的标识。你可以传这个值给 window.cancelAnimationFrame(ID)以取消本次回调函数。

    3、注意点

    刚开始接触requestAnimationFrame时,大家经常有疑问:requestAnimationFrame什么时候执行?执行后时浏览器刷新都会执行回调函数吗?下边做简单回答(说多了,我就露馅了)

    • 执行时机: 你调用一次requestAnimationFrame,从调用开始计时,下次浏览器刷新前,会执行回调函数。需要注意的是 :调用一次,就只执行一次回调,若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用requestAnimationFrame(类似递归,下边会有介绍)
    • 执行时间间隔:每次执行时间间隔会有差异,回调函数会被传入DOMHighResTimeStamp参数,DOMHighResTimeStamp指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。在同一个帧中的多个回调函数,它们每一个都会接受到一个相同的时间戳,即使在计算上一个回调函数的工作负载期间已经消耗了一些时间。该时间戳是一个十进制数,单位毫秒,最小精度为1ms(1000μs)。

    4、和setInterval、setTimeout的区别

    上边提到requestAnimationFrame可以理解是一个定时器,大家就肯定想到了setInterval、setTimeout,他们有什么区别?

    • 队列执行优先级不同:三者都是是异步 api,setTimeout 和 setInterval属于宏任务; requestAnimationFrame属于“渲染任务”(调用GUI 引擎),执行优先级在宏任务前,微任务之后。
    //每个tick执行逻辑如下:
    ...->上一个宏任务 -> 微任务(下一个宏任务前的所有微任务) -> 渲染任务 -> 下一个宏任务 ->...
    

    代码测试如下:

    setTimeout(function(){
      console.log("我是setTimeout", new Date().getTime())
    },5)
    
    requestAnimationFrame(function(){ //浏览器刷新频率是16.7ms左右,远大于5ms
      console.log("我是requestAnimationFrame", new Date().getTime());
    })
    
    new Promise(function(resolve){
      resolve('我是微任务')
    }).then(res=>{
      console.log(res, new Date().getTime());
    })
    //下边是打印信息:
    
    //我是微任务 1662356658402
    //我是requestAnimationFrame 1662356658403
    //我是setTimeout 1662356658408
    
    

    setTimeout写在requestAnimationFrame前,却在requestAnimationFrame后执行,而且通过打印时间戳可以看到相隔时间就是5ms左右,就是说明在执行requestAnimationFrame 后setTimeout才开始执行

    典型的 MacroTask(宏任务) 包含了 setTimeout, setInterval, setImmediate, requestAnimationFrame, I/O, UI rendering等;
    MicroTask(微任务)包含了 process.nextTick, Promises, Object.observe, MutationObserver等。

    • 定时时间不同:setTimeout 和 setInterval的定时时间是我们自己定的,而requestAnimationFrame的定时时间是浏览器的刷新间隔时间(一般浏览器1s刷新60次,刷新间隔时间大约为16.7s,所以不同浏览器刷新频率不同会导致定时时间不同),不用自己计算渲染时间,而且不会掉帧卡顿。

    二、应用

    1、基本应用

    用法和setTimeout差不多,只是不用写定时时间。需要注意的是每调用一次requestAnimationFrame,只在下次浏览器刷新前执行一次回调函数,如果希望多次连贯执行回调,则需要在回调中再次通过requestAnimationFrame调用回调函数(递归)。

    var animationId;//用来赋值requestAnimationFrame的id,为之后取消它做准备
    function step(){
        console.log('我就是下次浏览器刷新前需要执行的下一帧动画');
        animationId = requestAnimationFrame(step);//为了在之后的每次浏览器刷新前都执行回调,递归调用回调
    }
    animationId = requestAnimationFrame(step);//最开始的调用
    ···
    cancelAnimationFrame(animationId)//在满足某个条件时,取消上边requestAnimationFrame的调用,终止无休止的执行回调。
    

    2、兼容处理

    firefox、chrome、ie10以上, requestAnimationFrame 的支持很好,但不兼容 IE9及以下浏览器,所以需要在多个不同浏览器运行的话,就要做兼容性处理。


    在不同浏览器的兼容情况
    //简单的兼容性处理
    window.requestAnimationFrame = (function() {
      return window.requestAnimationFrame ||
             window.webkitRequestAnimationFrame ||
             window.mozRequestAnimationFrame ||
             function(callback) {
              window.setTimeout(callback, 1000/60);
             }
    })();
    

    上边这个简单的兼容处理还是存在问题的,因为并不是所有的设备的绘制时间间隔是1000/60ms,以及上面并没有cancel相关方法,所以,就有了下面这份更全面的兼容方法.

    (function() {
      var lastTime = 0;
      var vendors = ['webkit', 'moz'];
      //如果window.requestAnimationFrame为undefined先尝试浏览器前缀是否兼容
      for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
        window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] ||//webkit中此取消方法的名字变了
                                      window[vendors[x] + 'CancelRequestAnimationFrame'];
      }
      //如果仍然不兼容,则使用setTimeOut进行兼容操作
      if(!window.requestAnimationFrame) {
        window.requestAnimationFrame = function(callback, element) {
          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; 
        }
      }
     
      if(!window.cancelAnimationFrame) {
        window.cancelAnimationFrame = function(id) {
          clearTimeout(id);
        }
      }
    })();
    

    上述的代码是由Opera浏览器的技术师Erik Möller设计的,使得更好得兼容各种浏览器,但基本上他的代码就是判断使用4ms还是16ms的延迟,来最佳匹配60fps。

    三、总结和补充

    1、requestAnimationFrame优点

    requestAnimationFrame被专门用来处理动画,自然有他的优点存在的

    • 动画流畅:动画每一帧的执行的间隔时间紧跟浏览器的刷新频率,动画更流畅,不会掉帧。
    • 节能
      1. 首先,在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回流;
      2. requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览器会自动优化方法的调用,如果浏览器在后台运行或者该页面tab在后台运行时,动画会自动暂停。

    2、渲染引擎GUI

    GUI 渲染引擎,用来处理浏览器的渲染操作,在 js 中渲染操作也是异步的。比如 操作DOM的代码会在事件队列中生成一个渲染任务,js 执行到这个任务时就会去调用 GUI 引擎渲染。

    浏览器为了能够使得JS内部macro-task(宏任务)与DOM任务能够有序的执行(如果在 GUI 渲染的时候,js 改变了dom,那么就会造成渲染不同步),会在一个macro-task执行结束后,在下一个macro-task执行开始前,调用 GUI 引擎渲染对页面进行重新渲染,并会阻塞 js引擎计算。而micro-task(微任务)不涉及DOM操作,在渲染任务前执行。

    3、屏幕绘制频率相关补充

    即图像在屏幕上更新的速度,也即屏幕上的图像每秒钟出现的次数,它的单位是赫兹(Hz)。 对于一般笔记本电脑,这个频率大概是60Hz, 可以在桌面上 右键 > 屏幕分辨率 > 高级设置 > 监视器 中查看和设置。这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响,原则上设置成让眼睛看着舒适的值都行。
    市面上常见的显示器有两种,即 CRT和 LCD, CRT 是一种使用阴极射线管(Cathode Ray Tube)的显示器,LCD 就是我们常说的液晶显示器( Liquid Crystal Display)。
    CRT 是一种使用阴极射线管的显示器,屏幕上的图形图像是由一个个因电子束击打而发光的荧光点组成,由于显像管内荧光粉受到电子束击打后发光的时间很短,所以电子束必须不断击打荧光粉使其持续发光。电子束每秒击打荧光粉的次数就是屏幕绘制频率。
    而对于 LCD 来说,则不存在绘制频率的问题,因为 LCD 中每个像素都在持续不断地发光,直到不发光的电压改变并被送到控制器中,所以 LCD 不会有电子束击打荧光粉而引起的闪烁现象。
    因此,当你对着电脑屏幕什么也不做的情况下,显示器也会以每秒60次的频率正在不断的更新屏幕上的图像。为什么你感觉不到这个变化? 那是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的。而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1次/秒,屏幕上的图像就会出现严重的闪烁,这样就很容易引起眼睛疲劳、酸痛和头晕目眩等症状。

    4、参考文章:

    相关文章

      网友评论

          本文标题:你有用过requestAnimationFrame吗?

          本文链接:https://www.haomeiwen.com/subject/glqzgrtx.html