背景

前几天看js性能优化的时候,看见requestAnimationFrame这个函数,其实以前也有见到多这个函数,当时只搜了一下它的用法,并没有深入的了解,这次趁热打铁,进行了进一步的了解

  1. requestAnimationFrame到底是个啥?干嘛用的?
    嗯,那就先来说说这个函数是个啥吧!!!
    我们在做动画效果的时候,对于js通常会用到setTimeout or setInterval,但是定时器的间隔时间并不怎么准确。requestAnimationFrame的作用和定时器差不多,是浏览器专门为动画提供的API,相比定时器,多了一些优势。它的回调函数执行间隔时间时和显示器刷新频率是一致的。
  2. requestAnimationFrame的用法
	1.handle = requestAnimationFrame(callback);
	2.cancelAnimationFrame(handle)

第一点:
handle:一个非零整数,是在requestAnimationFrame回调函数列表中的唯一标识。在删除动画的时候,就需要用到这个唯一标识了
callback:回调函数,想要实现的效果都写在这里。下次重新绘制的时候执行,只接受一个参数,一个高精度时间戳,是函数执行时的时间。
第二点:
取消操作,无返回值
先看一个栗子

   let deg = 0;
      let id = null;
      let box = document.querySelector('.box')
      let stop = document.querySelector('.stop')
      box.onclick = function() {
         requestAnimationFrame(function change() {
            deg++;
            box.style.transform = 'rotate('+ deg +'deg)'
            id = requestAnimationFrame(change)
         })
      }
      stop.onclick = function() {
         cancelAnimationFrame(id)
      }
  1. 原理

动画帧请求回调函数列表:每个Document都有一个动画帧请求函数列表,每个元素可看成是<handle,callback>元组。开始时,列表是空的。当调用requestAnimationFrame函数时,会将包含回调函数的元组加入到动画帧请求回调函数列表的末尾。而当这个列表不为空时,浏览器会定期采样所有动画,并且将其加入到列队中。

关于采样所有动画任务的执行步骤,这篇文章:深入理解requestAnimationFrame用伪代码来进行了说明,如下:

var list = {};

var browsingContexts = 浏览器顶级上下文及其下属的浏览器上下文;

for (var browsingContext in browsingContexts) {
/*!将时间值从 DOMTimeStamp 更改为 DOMHighResTimeStamp 是 W3C 针对基于脚本动画计时控制规范的最新编辑草案中的最新更改,并且某些供应商仍将其作为 DOMTimeStamp 实现。较早版本的 W3C 规范使用 DOMTimeStamp,允许你将 Date.now 用于当前时间。
如上所述,某些浏览器供应商可能仍实现 DOMTimeStamp 参数,或者尚未实现 window.performance.now 计时函数。因此需要用户进行polyfill
*/
    //var time = 从1970年1月1日到当前所经过的毫秒数;
    var time = DOMHighResTimeStamp   //从页面导航开始时测量的高精确度时间。DOMHighResTimeStamp 以毫秒为单位,精确到千分之一毫秒。此时间值不直接与 Date.now() 进行比较,后者测量自 1970 年 1 月 1 日至今以毫秒为单位的时间。如果你希望将 time 参数与当前时间进行比较,请使用当前时间的 window.performance.now。



   var d = browsingContext的active document;   //即当前浏览器上下文中的Document节点
    //如果该active document可见
    if (d.hidden !== true) { 
        //拷贝active document的动画帧请求回调函数列表到list中,并清空该列表
        var doclist = d的动画帧请求回调函数列表
        doclist.appendTo(list);
        clear(doclist);
    }

    //遍历动画帧请求回调函数列表的元组中的回调函数
    for (var callback in list) {
        if (callback.cancelled !== true) {
            try {
                //每个browsingContext都有一个对应的WindowProxy对象,WindowProxy对象会将callback指向active document关联的window对象。
                //传入时间值time
                callback.call(window, time);
            }
            //忽略异常
            catch (e) {    
            }
        }
    }
}
  • 页面可见:当页面最小化或者被切换成后台标签页时,页面为不可见浏览器会触发一个visibilitychange事件,并将document.hidden属性设置为true;页面切换到显示状态时,页面可见,也同样触发一个visibilitychange事件,设置document.hidden为false。

  • 浏览器的执行过程:
    浏览器的执行的过程中,会先判断页面是否可见(即判断document.hidden的属性值),
    若可见,则将当前浏览器上下文中的Doucment节点的动画帧请求回调函数列表拷贝到list函数中,并清空该列表。最后遍历动画请求回调列表元组中的回调函数。
    若不可见,则不执行

     let count = 0;
      let id = null;
      requestAnimationFrame(function change() {
         if(count < 500){
        	 count++;
            id = requestAnimationFrame(change)
         }
         console.log(count)
      })
  • notes:
    当页面可见且不为空时,列表会被拷贝到list中,然后被清空。如果需要多次执行回调函数,则需要多次调用requestAnimationFrame函数,这也就是为什么在requestAnimationFrame需要递归的原因。
  1. 优点
  • 防止掉帧
    定时器及requestAnimationFrame的执行都是在内存中对图像属性进行改变,这个改变必须等到下次浏览器重绘的时候才会更新到屏幕上。若在浏览器重绘的这段时间之间图像属性多次改变,等到浏览器重绘的时候,只会将最后一帧的变化更新到屏幕上。
    requestAnimationFrame则让回调函数执行的间隔时间与屏幕的刷新频率一致。这样就不会出现掉帧了。
  • 减少CPU损耗
    前面有说到,当页面不可见时,该页面的刷新任务会被系统暂停,requestAnimationFrame回调函数的执行与页面刷新同步,所以也不会执行;但是定时器会继续在后台执行任务。
     let count = 0;
      let timer = null;
      timer = setInterval(function () {
         if(count < 200){
            count++;
            console.log(count)
         }else{
            clearInterval(timer)
         }
         
      },10)

参考博客

深入理解requestAnimationFrame
你知道的requestAnimationFrame【从0到0.1】


版权声明:本文为huangqqdy原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/huangqqdy/article/details/96750070