requestAnimationFrame 刨根问底

简介: 开头引用一段 Google Developer Rendering Performance:当屏幕正在发生视觉变化时,您希望在适合浏览器的时间执行您的工作,也就是正好在帧的开头。保证 JavaScript 在帧开始时运行的唯一方式是使用 requestAnimationFrame。框架或示例可能使用 setTimeout 或 setInterval 来执行动画之类的视觉变化,但这种做法的问题是,回调将在帧中的某个时点运行,可能刚好在末尾,而这可能经常会使我们丢失帧,导致卡顿。(事实上,jQuery 目前的默认 animate 行为是使用 setTimeout!)

开头引用一段 Google Developer Rendering Performance:

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


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

6.jpg

什么是渲染帧?


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


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


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


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


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


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

7.jpg

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


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


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


8.gif

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


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


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

9.jpg

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


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


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


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

10.jpg

打开我们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

11.jpg


总结


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

相关文章
|
1月前
|
JavaScript 前端开发
【JavaScript】面试手撕节流
上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。
53 3
|
3月前
|
前端开发 JavaScript 程序员
如何实现一个让面试官拍大腿的防抖节流函数
如何实现一个让面试官拍大腿的防抖节流函数
|
18天前
|
前端开发
【前端学习】—函数节流(九)
【前端学习】—函数节流(九)
|
8月前
|
JavaScript 容器
弄懂事件委托
弄懂事件委托
42 0
|
8月前
|
存储 缓存 JavaScript
用three.js搞3个炫酷粒子出场
出场就要帅气!动画要酷!点进来就看粒子玩出新花样!让你拥有炫酷的粒子出场方式,一闪一闪亮晶晶好像许多的星星!!!
|
10月前
|
JavaScript 前端开发
常用的click事件居然这么多门道,赶紧卷
你真的了解 onclick 点击事件吗? onclick 是异步的还是同步的?事件的触发顺序你了解过吗?现在我就带你一起来了解一下 onclick 事件前后左右的一些事情。
105 0
|
前端开发
前端学习案例3-requestAnimationFrame之3
前端学习案例3-requestAnimationFrame之3
44 0
前端学习案例3-requestAnimationFrame之3
|
前端开发
前端学习案例2-requestAnimationFrame之2
前端学习案例2-requestAnimationFrame之2
30 0
前端学习案例2-requestAnimationFrame之2
|
前端开发
前端学习案例1-requestAnimationFrame之1
前端学习案例1-requestAnimationFrame之1
37 0
前端学习案例1-requestAnimationFrame之1
|
机器学习/深度学习 前端开发
学css动画很难? 不妨来看看这篇css动画
CSS动画 1. CSS动画的基本概念 动画是一种使元素从一种样式逐渐转变为另一种样式的效果。CSS动画是通过改变元素的样式来实现的,这些样式可以是元素的位置、大小、颜色、背景、边框等等。