实现动画效果的requestAnimationFrame()
最近在做活动动画时发现的一个知识点
window. requestAnimationFrame()
- 屏幕刷新频率
当你对着电脑屏幕什么也不做的情况下,显示器也会以每秒60次的频率正在不断的更新屏幕上的图像
而人眼感觉不到是因为人的眼睛有视觉停留效应,即前一副画面留在大脑的印象还没消失,紧接着后一副画面就跟上来了,这中间只间隔了16.7ms(1000/60≈16.7), 所以会让你误以为屏幕上的图像是静止不动的
ps: 以前看新闻联播的时候,会发现画面里的电脑屏幕总是一直闪烁。横条纹均匀的分布在屏幕中。大概就是这个原因吧 - 动画原理
根据上面的原理我们知道,你眼前所看到图像正在以每秒60次的频率刷新,由于刷新频率很高,因此你感觉不到它在刷新。而动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。 那怎么样才能做到这种效果呢?
刷新频率为60Hz的屏幕每16.7ms刷新一次,我们在屏幕每次刷新前,将图像的位置向左移动一个像素,即1px。这样一来,屏幕每次刷出来的图像位置都比前一个要差1px,因此你会看到图像在移动;由于我们人眼的视觉停留效应,当前位置的图像停留在大脑的印象还没消失,紧接着图像又被移到了下一个位置,因此你才会看到图像在流畅的移动,这就是视觉效果上形成的动画。
- setTimeout
- setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了
异步队列
中,只有当主线程上的任务执行完以后,才会去检查该队列里的任务是否需要开始执行,因此 setTimeout 的实际执行时间一般要比其设定的时间晚
一些 - 刷新频率受屏幕分辨率和屏幕尺寸的影响,因此不同设备的屏幕刷新频率可能会不同,而 setTimeout只能设置一个固定的时间间隔,这个时间不一定和屏幕的刷新时间相同
- setTimeout 设置的时间间隔和屏幕刷新的时间间隔不同会导致画面出现卡顿情况
- setTimeout的执行时间并不是确定的。在Javascript中, setTimeout 任务被放进了
首先要明白,setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像。假设屏幕每隔16.7ms刷新一次,而setTimeout每隔10ms设置图像向左移动1px, 就会出现如下绘制过程:
第0ms: 屏幕未刷新,等待中,setTimeout也未执行,等待中;
第10ms: 屏幕未刷新,等待中,setTimeout开始执行并设置图像属性left=1px;
第16.7ms: 屏幕开始刷新,屏幕上的图像向左移动了1px, setTimeout 未执行,继续等待中;
第20ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=2px;
第30ms: 屏幕未刷新,等待中,setTimeout开始执行并设置left=3px;
第33.4ms:屏幕开始刷新,屏幕上的图像向左移动了3px, setTimeout未执行,继续等待中;
…
从上面的绘制过程中可以看出,屏幕没有更新left=2px的那一帧画面,图像直接从1px的位置跳到了3px的的位置,这就是丢帧现象,这种现象就会引起动画卡顿。
4、requestAnimationFrame
与setTimeout相比,requestAnimationFrame最大的优势是由系统来决定回调函数的执行时机。
var progress = 0;
// 回调函数
function render() {
progress += 1; //修改图像的位置
if (progress < 100) {
//在动画没有结束前,递归渲染
window.requestAnimationFrame(render);
}
}
// 第一帧渲染
window.requestAnimationFrame(render);
- 代码片段
// 实现动画
function animate() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
for (var i in round)
// 移动路径
round[i].move();
requestAnimationFrame(animate)
}
// 生成雪花碎片
function init() {
for (var i = 0; i < initRoundPopulation; i++) {
round[i] = new Round_item(i, Math.random() * WIDTH + 1, Math.random() * HEIGHT * .5);
round[i].draw();
}
animate()
}
init()