就目前來講纽疟,
web
應(yīng)用實現(xiàn)動畫的方式有很多種斥滤,排除CSS3
自帶的transition
和animation
動畫屬性烂琴,這里講一下JavaScript
實現(xiàn)動畫的其中兩種方法弧械,requestAnimationFrame
&&setTimeout
。
-
前言
在開始分析之前譬挚,我們要了解幾個概念锅铅,
視覺暫留
屏幕刷新頻率
1.視覺暫留
??眼睛的另一個重要特是視覺惰,即光象一旦在視網(wǎng)膜上形成减宣,視覺將會對這個光象的感覺維持一個有限的時間盐须,這種生理現(xiàn)象叫做視覺暫留
。對于中等亮度的光刺激漆腌,視覺暫留時間約為50ms
至200ms
贼邓。當我們看屏幕的時候,雖然你什么也沒做闷尿,但是屏幕還是以特定的頻率在不停刷新塑径,只是這個刷新過程我們?nèi)庋圩R別到他的細微變化,這就是我們接下來要說的 屏幕刷新頻率
2.屏幕刷新頻率
??我們?nèi)粘5娘@示器填具,一般頻率在60Hz
左右统舀,意味著我們的屏幕每1
秒需要刷新60
次,也就是說每1000ms
需要更新60
次的屏幕圖像劳景,那么我們由此可以得出誉简,屏幕圖像更新一次所需要的時間間隔也就是16.7ms(1000/60≈16.7)
。
??由于人的眼睛具有視覺暫留效應(yīng)
盟广,且暫留時間為50ms
至200ms
闷串,也就是說人在看屏幕的時候,還沒等到你的大腦印象消失筋量,電腦屏幕就已經(jīng)更新了烹吵,所以這個間隔讓你感覺不到變化碉熄。
??那么屏幕刷新頻率是不是越大越好?我們可以大膽假設(shè)一下年叮,假如我有三個顯示器具被,刷新頻率分別為1Hz
玻募、60Hz
只损、200Hz
、那么對應(yīng)的更新周期時間分別為1000ms
七咧、16.7ms
跃惫、5ms
。也就是頻率越大艾栋,圖像更新的間隔就越短爆存,我們看到的畫面就會越穩(wěn)定,當達到一秒更新一次的時候蝗砾,這個時候我們就能夠感覺到明顯的屏幕閃爍先较,帶來視覺疲勞。
3.setTimeout
??setTimeout
說白了就是個延時計時器悼粮,通過設(shè)置固定的時間間隔闲勺,從而時間一到,執(zhí)行相應(yīng)的回調(diào)方法扣猫。這個過程不會考慮屏幕刷新頻率菜循,換句話講,它的時間是寫死的申尤。那么為什么會存在丟幀現(xiàn)象發(fā)生癌幕,通俗來說就是為什么使用setTimeout
我會感覺到卡頓,畫面不穩(wěn)定昧穿。
??1.setTimeout
執(zhí)行的時間與屏幕的刷新頻率不一致會導(dǎo)致丟幀現(xiàn)象勺远。我們不考慮異步問題,假設(shè)我們現(xiàn)在的屏幕設(shè)備是60Hz
的刷新頻率时鸵。那么我們圖像的更新周期也就是16.7ms
胶逢,我現(xiàn)在的動畫要求是每10ms
往下偏移1px
,那么這個丟幀現(xiàn)象是如何產(chǎn)生的寥枝?這里我通過一張圖來解釋一下宪塔。
分析結(jié)果:
- 第1次重繪(
16.7ms
):圖形偏移到1px
; - 第2次重繪(
33.4ms
):圖形偏移到3px
囊拜;丟失1px偏移
; - 第3次重繪(
50.1ms
):圖形偏移到4px
某筐; - 第4次重繪(
66.8ms
):圖形偏移到6px
;丟失1px偏移
; - 第5次重繪(
83.5ms
):圖形偏移到8px
冠跷;丟失1px偏移
;
......
實驗效果:
??所以根據(jù)分析結(jié)果以及實驗效果南誊,如果setTimeout
執(zhí)行的順序與屏幕的刷新頻率不一致身诺,會造成丟幀現(xiàn)象,從而視覺上帶給我們的就是不流暢抄囚。
??2.由于JavaScript
屬于單線程霉赡,而setTimeout
任務(wù)會被放入異步隊列,通俗來講就是它的執(zhí)行得等一等幔托,具體等什么穴亏,不知道,就是想再等等重挑。只有主線程的任務(wù)執(zhí)行完畢之后嗓化,才會輪到它去執(zhí)行,也就是說我雖然設(shè)置setTimeout
16.7ms
間隔去執(zhí)行動畫屬性改變谬哀,但是實際運行的時間可能會有所延遲刺覆。這個延遲可能會導(dǎo)致執(zhí)行的時間與屏幕刷新的時間串掉,造成丟幀史煎。
分析結(jié)果:
- 第1次重繪(
16.7ms
):圖形未偏移谦屑;應(yīng)該偏移到1px
- 第2次重繪(
33.4ms
):圖形偏移到1px
;應(yīng)該偏移到2px
- 第3次重繪(
50.1ms
):圖形偏移到2px篇梭;
應(yīng)該偏移到3px
- 第4次重繪(
66.8ms
):圖形偏移到3px
氢橙;應(yīng)該偏移到4px
- 第5次重繪(
83.5ms
):圖形偏移到4px
;應(yīng)該偏移到5px
......
??所以根據(jù)分析結(jié)果很洋,我們可以看出充蓝,在異步的現(xiàn)象下,會造成一連串的執(zhí)行差喉磁,從而造成丟幀現(xiàn)象谓苟。
4.requestAnimationFrame
??官方解釋:window.requestAnimationFrame()
告訴瀏覽器——你希望執(zhí)行一個動畫,并且要求瀏覽器在下次重繪之前調(diào)用指定的回調(diào)函數(shù)更新動畫协怒。該方法需要傳入一個回調(diào)函數(shù)作為參數(shù)涝焙,該回調(diào)函數(shù)會在瀏覽器下一次重繪之前執(zhí)行。也就是說requestAnimationFrame
它不需要你去手動設(shè)置執(zhí)行間隔時間孕暇,它是跟隨系統(tǒng)的屏幕刷新頻率走的仑撞,如果屏幕刷新頻率是60Hz
,那么它的執(zhí)行間隔就是16.7ms(1000/60≈16.7)
妖滔,如果屏幕刷新頻率是100Hz
隧哮,那么它的執(zhí)行間隔就是10ms(1000/100=10)
,這樣就能夠保證它的執(zhí)行與屏幕的刷新頻率保持一致座舍,從而避免丟幀現(xiàn)象沮翔。
??為了提高性能和電池壽命,因此在大多數(shù)瀏覽器里曲秉,當requestAnimationFrame()
運行在后臺標簽頁或者隱藏的<iframe>
里時采蚀,requestAnimationFrame()
會被暫停調(diào)用以提升性能和電池壽命疲牵。
5.requestAnimationFrame Vs setTimeout 區(qū)別
-
requestAnimationFrame
在窗口隱藏的時候,會暫停調(diào)用榆鼠,而setTimeout
不會暫停纲爸。 -
requestAnimationFrame
跟隨系統(tǒng)屏幕刷新頻率,而setTimeout
手動配置妆够,會存在丟幀現(xiàn)象识啦。
代碼示例
var start = 0;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';
function step() {
start++;
element.style.top = start + 'px';
if (start < 500) {
window.requestAnimationFrame(step);
}
}
window.requestAnimationFrame(step);
6.附件:requestAnimationFrame Github 兼容降級處理
??源地址:https://github.com/darius/requestAnimationFrame
// Adapted from https://gist.github.com/paulirish/1579671 which derived from
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik M?ller.
// Fixes from Paul Irish, Tino Zijdel, Andrew Mao, Klemen Slavi?, Darius Bacon
// MIT license
if (!Date.now)
Date.now = function() { return new Date().getTime(); };
(function() {
'use strict';
var vendors = ['webkit', 'moz'];
for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) {
var vp = vendors[i];
window.requestAnimationFrame = window[vp+'RequestAnimationFrame'];
window.cancelAnimationFrame = (window[vp+'CancelAnimationFrame']
|| window[vp+'CancelRequestAnimationFrame']);
}
if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy
|| !window.requestAnimationFrame || !window.cancelAnimationFrame) {
var lastTime = 0;
window.requestAnimationFrame = function(callback) {
var now = Date.now();
var nextTime = Math.max(lastTime + 16, now);
return setTimeout(function() { callback(lastTime = nextTime); },
nextTime - now);
};
window.cancelAnimationFrame = clearTimeout;
}
}());