1. setTimeout
定时器一直是js动画的核心技术,但是它不够精准,因为定时器参数是指time 秒后放入异步队列中等待执行,如果前面有其他任务队列执行时间过长,则会导致动画延迟,效果不精准的问题。
requestAnimation的好处是,采用系统时间间隔(大多数浏览器刷新频率是60Hz, 相当于16.7ms刷新一次), 保持最佳的绘制效率, 不会因为间隔时间过短,造成多度绘制,增加开销。也不会因为时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制。 并且rAF会把每一帧中所有的DOM操作集中起来,再一次重绘或者回流中就完成。
setTimeout动画
setTimeout是通过设置一个间隔时间来不断地改变图像的位置,但是利用setTimeout实现的动画在某些低端机上会出现卡顿,抖动的现象。
setTimeout为什么会出现卡顿?
- setTimeout的执行时间并不是确定的,在javascript中,setTimeout任务被放入异步队列中等待执行,只有当主线程的任务执行完后,才会去检查该队列里的任务队列是否需要开始执行。因此setTimeout的实际执行时间一般要比其设定的时间晚些。
- 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新率可能不同,而setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同。
上述两种情况都会导致setTimeout的执行步调和屏幕的刷新率不一致,从而出现掉帧现象。
为什么步调不一致就会引起掉帧?
因为setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要到屏幕下次刷新时才会被更新到屏幕上,如果两者步调不一致, 就有可能会导致中间某一帧的操作被跨域过去,而直接更新下一帧的图像。
eg:
假设浏览器刷新频率是60HZ,即16.7ms刷新一次,而setTimeout每隔10ms设置图像向左移动1px,
- 0ms, 屏幕未刷新,waiting...
- 10ms 屏幕未刷新,waiting... setTimeout开始执行,并设置图像属性left = 1px
- 16.7ms 屏幕开始刷新,屏幕上的图像向左移动了1px, setTimeout 未执行,继续等待中
- 20ms 屏幕未刷新,waiting...setTimeout开始执行,并设置图像属性left = 2px
- 30ms: 屏幕未刷新,waiting...,setTimeout开始执行并设置left=3px;
- 33.4ms:屏幕开始刷新,屏幕上的图像向左移动了3px, setTimeout未执行,继续等待中;
从栗子中可知,屏幕没有更新left=2px的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,这种现象就会引起动画卡顿
2. requestAnimationFrame
在web应用中,实现动画效果的方法比较多,javascript中可以通过定时器setTimeout来实现,css3可以使用transition和animation来实现,html5中的canvas也可以实现。除此之外html5还提供一个专门用于请求动画的API, 即requestAnimationFrame(rAF), 直译就是 “请求动画帧”
rAF与setTimeout相比,最大的优势是由系统来决定回调函数的执行时机
. 具体一点,就是系统每次绘制之前会主动调用rAF中的回调函数
,如果系统绘制频率是60HZ, 那么回调函数就每16.7ms(1/60s = 1000/60ms)被执行一次, 如果绘制频率是75Hz,那么,这个间隔就变成了 1000/75 = 13.3ms。那就是说,rAF的执行步伐跟着系统的绘制频率走, 它能保证回调函数在屏幕每一次的绘制间隔中只被执行一次
, 这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。
下面再对比一个setTimeout和setInterval 。 对于动画来说,如果单帧的执行时间大于间隔时间,用setTimeout比用setInterval更保险,不过最好的方案还是用requestAnimationFrame
3. setInterval
var timer = setInterval(fn, delay) // setInterval会持续地调用指定的函数,知道timer被取消为止
clearInterval(timer)
timer(setInterve)首次触发后,它的事件进入异步队列中等待执行,当interval再次被触发时(这个时候timer的事件正在执行), 这一次它的事件就被丢弃了。 如果你在一个大的Javascript代码块内把所有的interval回调函数都囤起来的话,其记过就是在Javascript代码块执行完了之后会有一堆的interval事件被执行,而执行的过程中不会有间隔。因此,取代的做法是浏览器情缘先等一等,以确保在一个interval进入队列的时候队列中没有别的interval
setInterval(function(){
// 很长的代码块
},10)
取代方法: (用setTimeout取代setInterval)
setTimeout(function(){
// 很长的代码块
setTimeout(arguments.callee,10)
},10)
乍看上去,这两段代码在功能上似乎是相同的,可实际上并非如此。setTimeout的代码在前一次的回调执行完后总是至少会有10ms的延时(有 可能会更多,但是绝对不会更少);而setInterval则总是在每10ms的时候尝试执行一次回调,它不管上一次回调是什么时候执行的。
参考文献
requestAnimationFrame详解及对比
javascript线程解释(setTimeout,setInterval你不知道的事)
How JavaScript Timers Work