實現動畫效果的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()