requestAnimationFrame API
requestAnimationFrame
API
在 Web 应用中,实现动画效果的方法有:
- CSS3:Transition(过度) / Animation(动画)
- HTML5:Canvas
- JavaScript:setInterval(定时器) / requestAnimationFrame(请求动画帧) / jQuery
这篇文章主要介绍 requestAnimationFrame
请求动画帧,并尝试它的原理以及优势。
0. 了解相关概念
0.1 动画帧请求回调函数列表
每个 Document 都有一个动画帧请求回调函数列表,该列表可以看成是由一对 <handlerId, callback>
组成的集合。其中 handlerId
是一个整数,唯一地标识了数据在列表中的位置;callback
是回调函数。
0.2 屏幕刷新频率
屏幕刷新频率,即屏幕刷新的速度,通常由赫兹(Hz)表示,例如 60Hz 表示屏幕每秒刷新 60 次,也就是每 16.7ms 刷新一次。
0.3 动画如何实现的?
首先,屏幕上的图像以每秒60次的频率刷新,由于刷新频率很高,因此用户感觉不到它在刷新。而 动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?
刷新频率为 60Hz 的屏幕每 16.7ms 刷新一次,那么我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即 1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差 1px,这样用户就会看到图像在移动。而由于人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此才会看到图像在流畅的移动,这就是视觉效果上形成的动画。
0.4 浏览器如何标识页面是否处于可见状态?
当页面被最小化或者被切换成后台标签页时,页面为不可见,浏览器会触发一个 visibilitychange
事件,并设置 document.hidden
属性为 true
;切换到显示状态时,页面为可见,也同样触发一个 visibilitychange
事件,设置 document.hidden
属性为 false
。
1. 为什么需要 requestAnimationFrame
?
我们知道,有些 CSS 无法实现的动画,我们可以尝试用 js setInterval
来实现,大概是下面这样:
1 | (function() { |
该代码实现的功能是每隔100毫秒执行函数操作来达到动画效果,然而,使用计时器真的可靠吗?
在实际开发中,利用 setInterval
实现的动画在某些低端机上会出现卡顿、抖动的现象。 这种现象的产生有两个原因:
setInterval
的执行时间并不是确定的。在 Javascript 中,setInterval
任务(宏任务)被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此setInterval
的实际执行时间一般要比其设定的时间晚一些。- 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而
setInterval
只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致 setInterval
的执行步调和屏幕的刷新步调不一致,从而引起【丢帧】现象。
那为什么步调不一致就会引起丢帧呢?
首先,我们使用 setInterval
实现动画时,我们只是在内存中对图像的属性进行改变,而这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。那么如果改变图像属性和更新图像这两个操作的步调不一致,就可能会导致中间某一次的操作被跨越过去,而直接更新下一次改变后的图像。
举个例子,假设屏幕每隔 16.7ms 刷新一次,而 setInterval 每隔10ms设置图像向左移动1px, 就会出现如下绘制过程:
- 第 0 ms : 屏幕未刷新,等待中,
setInterval
也未执行,等待中; - 第 10 ms: 屏幕未刷新,等待中,
setInterval
开始执行并设置图像属性 left=1px; - 第 16.7 ms: 屏幕刷新,屏幕上的图像向左移动了 1px,
setInterval
未执行,继续等待中; - 第 20 ms: 屏幕未刷新,等待中,
setInterval
开始执行并设置left = 2px
; - 第 30 ms: 屏幕未刷新,等待中,
setInterval
开始执行并设置left = 3px
; - 第 33.4 ms: 屏幕刷新,屏幕上的图像向左移动了 3px,
setInterval
未执行,继续等待中; - …
可以看到,屏幕没有更新 left = 2px
那一帧的画面,图像直接从 1px 的位置跳到了 3px 的位置,这就是丢帧现象,用户看到的动画就卡顿了。
所以,requestAnimationFrame
的出现就是为了解决这个问题。与 setInterval
相比,requestAnimationFrame
最大的优势是由系统来决定回调函数的执行时机。具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms 被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame
的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
2. 使用 requestAnimationFrame
2.1 语法
1 | // 请求动画帧 |
2.2 实例
下面是一个简单的用 requestAnimationFrame
实现的动画:
1 |
|
3. requestAnimationFrame
的优势
CPU 节能:使用
setInterval
实现的动画,当页面被隐藏或最小化时,setInterval
仍然在后台执行动画任务,由于此时页面处于不可见或不可用状态,刷新动画是没有意义的,完全是浪费 CPU 资源。而requestAnimationFrame
则完全不同,当页面处于未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame
也会停止渲染,当页面被激活时,动画就从上次停留的地方继续执行,有效节省了 CPU 开销。函数节流:在高频率事件( resize / scroll 等 )中,为了防止在一个刷新间隔内发生多次函数执行,使用
requestAnimationFrame
可保证每个刷新间隔内,函数只被执行一次,这样既能保证流畅性,也能更好的节省函数执行的开销。一个刷新间隔内函数执行多次时没有意义的,因为显示器每 16.7ms 刷新一次,多次绘制并不会在屏幕上体现出来。
- 标题: requestAnimationFrame API
- 作者: 6dianbiqi
- 创建于 : 2025-02-21 13:36:13
- 更新于 : 2025-02-21 20:30:24
- 链接: https://6dianbiqi.com/2025/02/21/requestAnimationFrame-API/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。