requestAnimationFrame & 定时器
屏幕刷新频率:
屏幕刷新频率即图像在屏幕上更新的速度,即每秒图像更新的次数,它的单位是赫兹(Hz)。一般笔记本的值是60Hz。这个值受屏幕分辨率、屏幕尺寸、显卡影响。
市面上常见的显示屏有两种:XRT(传统显示屏)和LCD(液晶显示屏)。
CTR是一种使用电子阴极管的显示器,屏幕上的图像是由一个个因电子束击打而发光的荧光点组成,由于显像管内荧光粉受到电子束击打后发光时间很短,所以电子束必须不断击打荧光粉使其不断发光。电子束每秒击打荧光粉的次数就是屏幕更新频率。
LCD即液晶显示器,就不需要更新。因为LCD中每个像素都在持续不断的发光,所以LCD不会有电子束击打荧光粉而引起的闪烁现象。
so,即使你对着显示器什么都不做,显示器也会以每秒60次的频率正在不断的更新屏幕上的图像。
动画原理
动画的本质是让人眼看到图像被刷新而引起变化的视觉效果是以连贯的、平滑的方式进行过渡的。
前面我们已经知道显示器一直在刷新图像,但是我们并没有感觉变化,是因为刷新频率很高,我们感觉不到而已。
举个例子:刷新频率为60Hz的屏幕每16.7ms刷新一次,在屏幕刷新前将图像的位置向左移动1px,这样的话,每次屏幕刷新之后的位置都和原来差1px,因此我们就看到图像在动了。由于人眼的视觉停留效应,当前位置的图像停留在大脑中的印象还没有消失,紧接着图像又移动到下一个位置,因此看到图像是在流畅的移动,这就形成了视觉上的动画。
setTimeout
setTimeout是设置一个时间间隔来不断的改变图像的位置,而达到动画效果。但是setTimeout在某低端机上会出现卡顿、抖动的现象。原因如下:
- setTimeout的执行时间不是确定的。在javascript中,将setTimeout任务会被放进异步队列,只有主线程上的任务执行完以后,才会去检查异步队列中的任务是否需要开始执行,so setTimeout的实际执行时间一般要比设定的时间晚一点
- 刷新频率收到屏幕分辨率和屏幕尺寸影响,所以不同的屏幕设置同一个时间间隔,并不一定和屏幕的刷新时间相同,可能会出现丢帧。
setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须等到屏幕下次刷新时才会更新到屏幕上。如果两者步调不一致,就出现丢帧了。假设屏幕每个16.7ms更新一次,而setTimeout每10ms设置图像向左移动1px,绘制过程如下:
- 第0ms: 屏幕刷新,等待中,setTimeout未执行,等待中
- 第10ms: setTimeout开始执行并设置图像属性left= 1px;
- 第16.7ms:屏幕开始刷新,屏幕上的图像向左移动1px;
- 第20ms: setTimeout开始执行并设置图像属性left= 2px
- 第30ms: setTimeout开始执行并设置图像属性left= 3px;
- 第33.4ms:屏幕开始刷新,屏幕上的图像向左移动3px;
- ......
上面这种情况,屏幕没有更新left = 2px的那一帧图像。图像直接从1px的位置跳到3px的位置,这就是丢帧现象,这种想象就会引起动画的卡顿。
requestAnimationFrame
与setTimeout相比,requestAnimationFrame最大的优势就是由系统决定回调函数的执行时间。requestAnimationFrame的步伐跟着系统的刷新步伐,它能保证回调函数在屏幕每次的刷新间隔中只被执行一次,这样就不会丢帧,也不会导致动画卡顿。
除此之外还有两大优势:
- CPU节能:使用setTimeout实现的动画,当页面被隐藏或者最小化时,setTimeout仍在后台执行动画任务,但是刷新动画也没有意义,因为页面是不可见的,完全是浪费CPU资源。而requestAnimationFrame则完全不同,当页面处理为未激活的状态下,该页面的屏幕刷新任务也会被系统暂停,因此跟着系统步伐走的requestAnimationFrame也会停止渲染,当页面被激活时,动画就从上次挺溜的地方继续执行,而且节省CPU开销。
- 函数节流:在高频事件(resize,scroll)中,为防止一个刷新间隔多次函数执行,使用requestAnimationFrame可保证每个刷新间隔,函数只被执行一次,这样既保证了流畅性,又更好的节省了函数执行的开销。多次执行是没有意义的,因为显示器每16.7秒刷新一次,多次绘制并不会在屏幕上体现出来。
requestAnimationFrame定义:
window.requestAnimationFrame():告诉浏览器,你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传一个回调函数作为参数,该回调函数在浏览器下一次重绘之前执行。
注意:若你想在浏览器下次重绘之前继续更新下一帧动画,那么回调函数自身必须再次调用window.requestAnimationFrame();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
#box{
margin: 0 auto;
width: 50px;
height: 50px;
background: green;
margin-top: 100px;
}
</style>
</head>
<body>
<div id="box"></div>
<script>
function animationWidth() {
var div = document.getElementById('box');
div.style.width = parseInt(div.offsetWidth) + 1 + 'px';
if(parseInt(div.style.width) < 200) {
requestAnimationFrame(animationWidth)
}
}
requestAnimationFrame(animationWidth);
</script>
</body>
</html>