首先從字面意思來看,就是請求動畫幀(后面用raf代替)底燎。
說起動畫蠢络,css3的transition和animation,js的setTimeout和setInterval效览,jquery的animate()都可以做動畫无切,這幾種方式的性能和效率也有不同。今天聊的這個也可以做動畫丐枉。
是什么
說白了就是做幀動畫用的哆键,所謂的請求動畫幀,是告訴瀏覽器在下一次屏幕刷新的時候瘦锹,別忘了運行raf這個方法籍嘹,這一塊要注意一點:每次屏幕刷新的時候,都會運行raf弯院。也就是raf的運行頻率是根據(jù)屏幕刷新頻率來決定的辱士。
性能怎么樣
首先聊聊setTimeout。
setTimeout 其實就是通過設(shè)置一個間隔時間來不斷的改變圖像的位置听绳,從而達到動畫效果的识补。但我們會發(fā)現(xiàn),利用seTimeout實現(xiàn)的動畫在某些低端機上會出現(xiàn)卡頓辫红、抖動的現(xiàn)象凭涂。 這種現(xiàn)象的產(chǎn)生有兩個原因:
1.setTimeout的執(zhí)行時間并不是確定的。在Javascript中贴妻, setTimeout 任務(wù)被放進了異步隊列中切油,實際上只是指定了把動畫代碼添加到瀏覽器UI線程等待執(zhí)行的時間,如果隊列前面有其他任務(wù)名惩,只有當主線程上的任務(wù)執(zhí)行完以后澎胡,才會去檢查該隊列里的任務(wù)是否需要開始執(zhí)行,因此 setTimeout 的實際執(zhí)行時間一般要比其設(shè)定的時間晚一些娩鹉。
2.刷新頻率受屏幕分辨率和屏幕尺寸的影響攻谁,因此不同設(shè)備的屏幕刷新頻率可能會不同,而 setTimeout只能設(shè)置一個固定的時間間隔弯予,這個時間不一定和屏幕的刷新時間相同戚宦。
以上兩種情況都會導致setTimeout的執(zhí)行步調(diào)和屏幕的刷新步調(diào)不一致,從而引起丟幀現(xiàn)象锈嫩。 那為什么步調(diào)不一致就會引起丟幀呢受楼?
首先要明白垦搬,setTimeout的執(zhí)行只是在內(nèi)存中對圖像屬性進行改變,這個變化必須要等到屏幕下次刷新時才會被更新到屏幕上艳汽。如果兩者的步調(diào)不一致猴贰,就可能會導致中間某一幀的操作被跨越過去,而直接更新下一幀的圖像河狐。假設(shè)屏幕每隔16.7ms刷新一次米绕,而setTimeout每隔10ms設(shè)置圖像向左移動1px, 就會出現(xiàn)如下繪制過程:
第0ms: 屏幕未刷新馋艺,等待中义郑,setTimeout也未執(zhí)行,等待中丈钙;
第10ms: 屏幕未刷新非驮,等待中,setTimeout開始執(zhí)行并設(shè)置圖像屬性left=1px雏赦;
第16.7ms: 屏幕開始刷新劫笙,屏幕上的圖像向左移動了1px, setTimeout 未執(zhí)行星岗,繼續(xù)等待中填大;
第20ms: 屏幕未刷新,等待中俏橘,setTimeout開始執(zhí)行并設(shè)置left=2px;
第30ms: 屏幕未刷新允华,等待中,setTimeout開始執(zhí)行并設(shè)置left=3px;
第33.4ms:屏幕開始刷新寥掐,屏幕上的圖像向左移動了3px靴寂, setTimeout未執(zhí)行,繼續(xù)等待中召耘;
…
從上面的繪制過程中可以看出百炬,屏幕沒有更新left=2px的那一幀畫面,圖像直接從1px的位置跳到了3px的的位置污它,這就是丟幀現(xiàn)象剖踊,這種現(xiàn)象就會引起動畫卡頓。所以丟幀現(xiàn)象可以說是因為屏幕刷新頻率和代碼執(zhí)行不對稱導致的衫贬。
那如果屏幕刷新頻率和代碼執(zhí)行對稱的話德澈,是不是就不會發(fā)生丟幀現(xiàn)象了?固惯?梆造。raf的誕生就解決了這一點,上文已經(jīng)說了缝呕,raf的執(zhí)行頻率是根據(jù)屏幕刷新頻率來說的澳窑,有圖像的變化就會刷新屏幕,刷新屏幕就會運行raf供常,這樣就解決了丟幀的現(xiàn)象摊聋。
如何用
raf()會接收一個參數(shù),這個參數(shù)是一個函數(shù)栈暇,函數(shù)的作用就是負責改變下一次重繪時dom的樣式麻裁。
var i = 0;
function updateDom(){
let box = document.getElementById('box');
i+=10;
box.style.width = 300 + i +'px';
if(i<500){
requestAnimationFrame(updateDom)
}
}
requestAnimationFrame(updateDom)
到此,raf解決了動畫什么時候開始和最佳循環(huán)循環(huán)間隔的問題源祈,但是代碼具體的在哪個時間執(zhí)行嗎煎源,確是不知道的,只知道動畫在屏幕刷新時候開始香缺。不過手销,mozRequestAnimationFrame()傳遞的函數(shù)也會接收一個參數(shù),是一個時間碼(從1970年1月1日起至今的毫秒數(shù))图张,表示下一次重繪發(fā)生的實際時間锋拖。
如果想知道距離上一次重繪過去的時間,可以查詢mozAnimationStartTime祸轮,不過這個屬性知識Mozilla實現(xiàn)了兽埃。目前chrome和IE都沒有此屬性。這個值包含上一次重繪的時間碼,用傳入回調(diào)函數(shù)的時間碼減去這個時間适袜,就能計算出在屏幕上重繪下一簇變化之前要經(jīng)過多少時間柄错。
function draw(timestamp){
var differ = timestamp - startTime;//differ確定下一次重繪時間與現(xiàn)在時間的差值
startTime = timestamp;//把timestamp重寫為這一次的繪制時間
mozRequestAnimationFrame(draw);
}
var startTime = mozAnimationStartTime;
mozRequestAnimationFrame(draw)
chrome和IE10+都給出了帶前綴的requestAnimationFrame方法實現(xiàn),不過與Mozilla的版本還是有點差異苦酱。
差異1:不回給回調(diào)函數(shù)傳時間碼售貌。
差異2:chrome增加了第二個可選的參數(shù):即將要發(fā)生變化的DOM元素。
chrome提供了一個取消幀動畫的方法疫萤,webkitCancelAnimationFrame(),用于取消之前計劃的重繪操作趁矾。
當然,作為一個新興的API给僵,兼容性問題肯定少不了毫捣。所以實際開發(fā)過程中,請注意兼容性的處理帝际。
requestAnimationFrame的優(yōu)點:
1.多個raf可以并行繪制蔓同。(這一點跟setTimeout和setInterval進入異步隊列有關(guān)系。)
2.在隱藏或者不可見的元素匯總蹲诀,raf將不會進行重繪和回流斑粱,這就意味著更少的CPU和GPU以及內(nèi)存使用量。
- 由瀏覽器專門為動畫提供的API脯爪,在運行時瀏覽器會自動優(yōu)化方法的調(diào)用则北,并且如果頁面不是激活狀態(tài)的話矿微,動畫就會暫停,節(jié)省了CPU開銷尚揣。(對于這一點涌矢,本人試了試setTimeout和setInterval,發(fā)現(xiàn)用這兩者做的動畫在不激活的狀態(tài)下也會暫停快骗,不知道是chrome瀏覽器做了優(yōu)化還是說只有chrome是這樣娜庇。)
4.函數(shù)節(jié)流: 在高頻率時間中(resize,scroll等),為了防止在一個刷新間隔內(nèi)發(fā)生多次函數(shù)執(zhí)行方篮,使用raf可保證每個刷新間隔內(nèi)名秀,函數(shù)只能被執(zhí)行一次,這樣既能保證流浪性藕溅,也能更好的節(jié)省函數(shù)執(zhí)行的開銷匕得。