當(dāng)我們要在頁(yè)面上實(shí)現(xiàn)一些動(dòng)畫效果的時(shí)候疼约,通常會(huì)考慮兩種方式:
1、通過(guò)css3的animation+keyframes蝙泼,或者transition
2程剥、通過(guò)setTimeout或者setInterval。
毫無(wú)疑問(wèn)汤踏,首選的方案是第一種织鲸。而當(dāng)需要考慮兼容性,或者需要精確的控制動(dòng)畫的時(shí)候溪胶,無(wú)可避免地會(huì)使用setTimeout/setInterval來(lái)實(shí)現(xiàn)動(dòng)畫搂擦。這種實(shí)現(xiàn)方式是低效的,而且在當(dāng)前時(shí)間點(diǎn)哗脖,可以考慮更高效的實(shí)現(xiàn)方式瀑踢。
什么是流暢
頁(yè)面的每一幀都是系統(tǒng)通過(guò)CPU或者GPU繪制出來(lái)的,其繪制的最高幀率受限于顯示器的刷新頻率才避。鑒于大多數(shù)的顯示器刷新頻率都是60Hz橱夭,頁(yè)面的最大繪制幀率就是60fps(frame per second)。
因此一個(gè)動(dòng)畫最理想的情況就是60fps桑逝。這就意味著我們需要在把每一幀的繪制時(shí)間限制在16.7毫秒(1000/60)棘劣,壓力山大。
setTimeout/setInterval的問(wèn)題
首先楞遏,計(jì)時(shí)并不精確茬暇。setTimeout和setInterval的計(jì)時(shí)依賴瀏覽器內(nèi)置時(shí)鐘,而內(nèi)置時(shí)鐘的精確度又依賴于時(shí)鐘的更新頻率橱健,在IE8及以下版本的瀏覽器中而钞,這個(gè)更新頻率是15.6ms。也就意味著拘荡,如果把timer的間隔設(shè)成16.7ms,我們需要經(jīng)歷兩個(gè)時(shí)鐘更新周期才會(huì)觸發(fā)timer撬陵,延時(shí)了14.5ms(15.6 * 2 – 16.7)珊皿。
其次网缝,由于單線程與異步隊(duì)列,當(dāng)動(dòng)畫兩幀之間有一個(gè)復(fù)雜任務(wù)時(shí)蟋定,第二幀的繪制會(huì)一直等待這個(gè)任務(wù)完畢才會(huì)開(kāi)始粉臊,必然會(huì)帶來(lái)卡頓現(xiàn)象。
假使計(jì)時(shí)是精確的(以14ms為例)驶兜,任務(wù)隊(duì)列也不存在阻塞狀態(tài)扼仲,是不是就沒(méi)問(wèn)題了?我們看下圖抄淑,紅色幀丟失屠凶。
requestAnimationFrame
requestAnimationFrame
這里并不討論requestAnimationFrame的定義,具體可以看:
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
從上面的描述中肆资,我們可以總結(jié)出影響一個(gè)動(dòng)畫的兩個(gè)關(guān)鍵因素:
1矗愧、開(kāi)始繪制一幀的時(shí)機(jī)。
2郑原、繪制一幀需要的時(shí)間唉韭。
requestAnimationFrame這個(gè)原生API可以自動(dòng)幫我們?cè)O(shè)置動(dòng)畫的幀率,我們只需要告訴它犯犁,我們需要繪制新的一幀属愤,請(qǐng)?jiān)诶L制下一幀的時(shí)候調(diào)用我的回調(diào)函數(shù)。這保證了上面的關(guān)鍵因素的第一點(diǎn)酸役。對(duì)于第二點(diǎn)requestAnimationFrame是沒(méi)有辦法的春塌,但是它可以在繪制時(shí)間比較長(zhǎng)的時(shí)候,把動(dòng)畫幀率從60fps變?yōu)?0fps以保證動(dòng)畫不卡頓簇捍。
requestAnimationFrame還處于草案階段只壳,瀏覽器的兼容性也有限:
Polyfill
requestAnimationFrame
不同的瀏覽器對(duì)于requestAnimationFrame定義存在差異,而且對(duì)于不支持該特性的瀏覽器暑塑,我們需要降級(jí)到setTimeout的方案吼句。好在requestAnimationFrame和setTimeout的定義一致,比較方便做兼容事格。
兼容方法已經(jīng)有成熟的實(shí)現(xiàn)惕艳。
關(guān)于Layer
requestAnimationFrame
我們以Apple的主頁(yè)為例:
最上面的圖片輪轉(zhuǎn)動(dòng)畫里,每一頁(yè)都設(shè)置了transform:translateZ(0px)驹愚。為什么可以使用2D渲染就能實(shí)現(xiàn)的效果卻要用3D远搪?這就跟瀏覽器的渲染機(jī)制有關(guān)。
首先逢捺,使用3D的話谁鳍,瀏覽器會(huì)調(diào)用GPU而不是CPU來(lái)進(jìn)行頁(yè)面渲染,效率更高。
其次倘潜,瀏覽器渲染一個(gè)頁(yè)面的過(guò)程中绷柒,會(huì)把頁(yè)面元素分成若干的層(Layer),然后按層提交給GPU做貼圖渲染涮因。當(dāng)我們要改變某一個(gè)DOM的樣式時(shí)废睦,比如width或者h(yuǎn)eight,會(huì)觸發(fā)該DOM所在層的重繪养泡。一次操作還好嗜湃,但是如果是頻繁改變的動(dòng)畫,效率就會(huì)非常低澜掩。
Apple這里使用translate3d购披,就是為了單獨(dú)為動(dòng)畫頁(yè)建立Layer,進(jìn)而提高重繪的效率输硝。
總結(jié)
精度低的動(dòng)畫盡量使用css3來(lái)實(shí)現(xiàn)今瀑。
如果需要js來(lái)實(shí)現(xiàn)動(dòng)畫,可以使用requestAnimationFrame点把。有兼容需求可以引入polyfill橘荠。
盡量把動(dòng)畫DOM放在獨(dú)立的Layer中。
本文作者:高原(點(diǎn)融黑幫)郎逃,現(xiàn)任點(diǎn)融成都團(tuán)隊(duì)高級(jí)前端開(kāi)發(fā)哥童,四川大學(xué)計(jì)算機(jī)碩士。三年創(chuàng)業(yè)褒翰,一年外企經(jīng)驗(yàn)贮懈,專注于研究前端的各種框架與新技術(shù)。業(yè)余喜歡打籃球优训,司職中鋒朵你。