分享

requestAnimationFrame 刨根问底

 程序IT圈 2021-01-16

开头引用一段 Google Developer Rendering Performance:

当屏幕正在发生视觉变化时,您希望在适合浏览器的时间执行您的工作,也就是正好在帧的开头。保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame。

框架或示例可能使用 setTimeout 或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。(事实上,jQuery 目前的默认 animate 行为是使用 setTimeout!)

什么是渲染帧?

这得从显示器的刷新频率说起,目前主流的LCD液晶显示器,刷新频率规格大多在60Hz。

60Hz什么概念呢,就是大约每16.66毫秒刷新一次屏幕,叫做一个渲染帧。

你现在看到的屏幕,就是用这种高速在不断的做一次又一次的渲染。

在这个渲染帧到下个渲染帧期间,加上JS线程和GUI线程之间的通信等损耗,你的代码必须在10ms左右完成才能保证不掉帧。

是不是看高速世界看得有些懵?

没关系,我们换一个老式CRT显示器

CRT显示器是靠电子束激发屏幕内表面的荧光粉来显示图像的,由于荧光粉被点亮后很快会熄灭,所以电子枪必须循环地不断激发这些点,电子束在屏幕上一行紧接一行从左到右的逐行扫描。

现在我们来放慢它的速度,假装它扫描整个屏幕要用10秒,够长了吧~现在再来看刚刚的操作。

我们一个动画小球在屏幕左边,接着我们执行了一行代码,它右移了一个像素。但是它没有马上呈现在画面中,而是等到逐行扫描过后,才出现。(还得自己画gif  〒▽〒)

同理,回到现代设备,60Hz的刷新频率也是如此处理。

这么短的时间,代码能执行完吗?

回答这个问题之前,我们来看看现代的CPU(拿i3举例)

1GHz是多少次脉冲呢?大概是1秒10亿次~吧~

1GHz的CPU如果只做加法运算,进行一次完整的加法运算需要读2个数据,8个周期+运算16个周期+写入6个周期总共需要30个时钟周期(注意,不同CPU需要的周期是不同的,这里只是举列),大概也还能1秒做个100万次。

所以这里还得再看看我们的代码算法难易度

如何查看我们的代码执行时间?

打开我们Chrome的开发者工具,选择JavaScript Profiler就可以看见了(可以用下面的示例代码跑一跑,感受一下)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
    <style type="text/css">
      .ball {
        width: 50px;
        height: 50px;
        position: absolute;
        top: 0px;
        left: 40%;
        background: orange;
        border-radius: 50%;
      }
    
</style>
  </head>
  <body>
    <div class="ball"></div>
    <script>
      function move() {
        document.querySelector('.ball').style.top = 100 * Math.random() + 100 + 'px';
        requestAnimationFrame(move);
        // 可以打开setTimeout注释对比性能
        // setTimeout(() => { move() })
      }
      move()
    
</script>
  </body>
</html>

优势与兼容性

requestAnimationFrame还有以下两个优势:

  • CPU节能:使用setTimeout实现的动画,当页面被隐藏或最小化时,setTimeout 仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了CPU开销。

  • 函数节流:在高频率事件(resize,scroll等)中,为了防止在一个刷新间隔内发生多次函数执行,使用requestAnimationFrame可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每16.7ms刷新一次,多次绘制并不会在屏幕上体现出来。

从图中也看出除了IE外的其他浏览器兼容性还是很不错的,大家也可以看看这篇优雅降级方案:

https://github.com/darius/requestAnimationFrame

总结

在写相关动画效果的时候,因当格外注意动画的代码,尽量在10ms内执行完成。与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机,在上个渲染帧结束后开始执行代码,规避出现掉帧的情况。

对技术感兴趣的同学可以Github互相关注一波~

https://github.com/cmyh100

--完--

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多