深入理解requestAnimationFrame并实现相册组件中的切换动画
Dreamsqin 人气:2
全手打原创,转载请标明出处:[https://www.cnblogs.comhttps://img.qb5200.com/download-x/dreamsqin/p/12529885.html](https://www.cnblogs.comhttps://img.qb5200.com/download-x/dreamsqin/p/12529885.html),多谢,=。=~
(如果对你有帮助的话请帮我点个赞啦)
> 通常情况下,我们利用HTML5的canvas,CSS3的transform、transition、animation实现动画效果,但是今天为了实现相册组件中`scrollLeft`改变的动效,怎么用js实现动画还不影响效果和性能~=。=,居然让我发现了一个神奇的存在:`requestAnimationFrame`,下面来深入学习一下。
## 效果展示
***动画的本质就是要让人眼看到图像被刷新而引起变化的视觉效果,而这个变化要以连贯的、平滑的方式进行过渡。*** 那如何从原理上实现这种效果呢?或者说怎么让改变显得不会那么突兀?在说明前需要先科普一下相关的小知识。
首先看一下最终实现的效果 ↓
##### 使用动画效果前:闪现式移动
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320094706797-2019277859.gif)
##### 使用动画效果后:平滑式过渡
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320094732053-1425270645.gif)
## 科普小知识
#### 1、屏幕刷新(绘制)频率
***指图像在屏幕上更新的速度,也就是屏幕上的图像每秒钟出现的次数,单位为赫兹(Hz)。***
对于一般笔记本电脑,这个频率大概是60Hz, 可以在桌面上`右键 > 屏幕分辨率 > 高级设置 > 监视器 > 屏幕刷新频率`中查看和设置。这个值的设定受屏幕分辨率、屏幕尺寸和显卡的影响,原则上设置成让眼睛看着舒适的值就可以了。
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320094826181-317396861.png)
**常见的两种显示器**:
**CRT**: 一种使用阴极射线管(Cathode Ray Tube)的显示器。屏幕上的图形图像是由一个个荧光点(因电子束击打而发光)组成,由于显像管内荧光粉受到电子束击打后发光的时间很短,所以电子束必须不断击打荧光粉使其持续发光。`电子束每秒击打荧光粉的次数就是屏幕刷新频率`。
**LCD**: 我们常说的液晶显示器( Liquid Crystal Display)。因为 LCD中每个像素在背光板的作用下都在持续不断地发光,直到不发光的电压改变并被送到控制器中,所以 LCD 不会有电子束击打荧光粉而引起的闪烁现象。
因此,当你对着电脑屏幕什么也不做的情况下,显示器也会以每秒60次的频率不断更新屏幕上的图像。
**为什么你感觉不到这个变化?**
那是因为人的眼睛有`视觉暂留`,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的。
而屏幕给你的这种感觉是对的,试想一下,如果刷新频率变成1Hz(1次/秒),屏幕上的图像就会出现严重的闪烁,这样很容易引起眼睛疲劳、酸痛和头晕目眩等症状。
#### 2、动画原理
根据`屏幕刷新频率`我们知道,你眼前所看到图像正在以每秒 60 次的频率绘制,由于频率很高,所以你感觉不到它的变化。
60Hz 的屏幕每 16.7ms 绘制一次,如果在屏幕每次绘制前,将元素的位置向右移动一个像素,即`Px += 1`,这样一来,屏幕每次绘制出来的图像位置都比前一个差1px,你就会看到图像在移动。
由于人眼的`视觉暂留`,***当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,所以你所看到的效果就是图像在流畅的移动。***这就是视觉效果上形成的动画。
感受一下↓(因为`视觉暂留`,让你感觉这个人在走动)
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320094935032-707212203.gif)
#### 3、Element.scrollLeft
要实现相册组件中照片的移动需要使用dom元素的一个很重要的属性`scrollLeft`,***可以读取或设置元素滚动条到元素左边的距离。***听上去好像有点儿绕,画个图看看:
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320095025386-205415803.png)
蓝色的是元素的滚动条,`scrollLeft`则是红段标注的滚动条到元素左边的距离,后续相册中图片的切换需要通过修改`scrollLeft`值实现。
## setTimeout实现动画
了解了动画原理后,假定在`requestAnimationFrame`出现以前,在JavaScript 中想要实现上述动画效果,怎么办呢?无外乎就是用`setTimeout`或`setInterval`,***通过设置一个间隔时间来不断改变图像的位置,从而达到动画效果***,本文以`setTimeout`为例。
```javascript
// demo1:
function moveTo(dom, to) {
dom.scrollLeft += 1;
if(dom.scrollLeft <= to) {
setTimeout(() => {
moveTo(dom, to)
}, 16.7)
}
}
```
但我们会发现,利用`setTimeout`实现的动画在某些低端机上会出现卡顿、抖动的现象。
**这种现象的产生有两个原因:**
> **`setTimeout`的执行时间并不是确定的。**在Javascript中, `setTimeout `任务被放进了异步队列中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此`setTimeout `的实际执行时间一般要比其设定的时间晚一些。
**刷新频率受屏幕分辨率和屏幕尺寸的影响。**因此不同设备的屏幕刷新频率可能会不同,而`setTimeout`只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
以上两种情况都会导致`setTimeout`的执行步调和屏幕的刷新步调不一致,从而引起丢帧现象。
**那为什么步调不一致就会引起丢帧呢?**
首先要明白,`setTimeout`的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跳过去,直接更新下一帧的图像。
**举个栗子~**
假设屏幕每隔16.7ms刷新一次,而`setTimeout`每隔10ms设置图像向右移动1px, 就会出现如下绘制过程:
![](https://img2020.cnblogs.com/blog/1074523/202003/1074523-20200320095049988-1883164465.png)
从上面的绘制过程中可以看出,屏幕没有更新`left = 2px`的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,会引起动画卡顿。而原因就是`setTimeout`的执行步调和屏幕的刷新步调不一致。
开发者可以用很多方式来减轻这些问题的症状,但彻底解决基本很难,**问题的根源在于时机**:
>**对于前端开发者来说**,`setTimeout`提供的是一个等长的定时器循环(timer loop),我们对于浏览器内核对渲染函数的响应以及何时能够发起下一个动画帧的时机,是完全不了解的。
**对于浏览器内核来说**,它能够了解发起下一个渲染帧的合适时机,但是对于任何 `setTimeout`传入的回调函数执行,都是一视同仁的。它很难知道哪个回调函数是用于动画渲染的,因此,优化的时机非常难以掌握。
总的来说就是,写 JavaScript 的人了解一帧动画在哪行代码开始,哪行代码结束,却不了解应该何时开始,应该何时结束,而在内核引擎来说,却恰恰相反,所以二者很难完美配合,直到 `requestAnimationFrame`出现。
## requestAnimationFrame实现动画
与`setTimeout`相比,`requestAnimationFrame`***最大的优势是由浏览器来决定回调函数的执行时机,即紧跟浏览器的刷新步调。***
具体一点讲,如果屏幕刷新频率是60Hz,那么回调函数每16.7ms被执行一次,如果屏幕刷新频率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,自然不会导致动画的卡顿。
```javascript
// demo2:
function moveTo(dom, to) {
dom.scrollLeft += 1;
if(dom.scrollLeft <= to) {
window.requestAnimationFrame(() => {
moveTo(element, to)
})
}
}
```
除此之外,`requestAnimationFrame`还有***以下两个优势***:
>**CPU节能:**使用`setTimeout`实现的动画,当页面被隐藏(隐藏的`
加载全部内容