摘要:相信不少人在做移動(dòng)端動(dòng)畫的時(shí)候遇到了卡頓的問(wèn)題,這篇文章嘗試從瀏覽器渲染的角度一點(diǎn)一點(diǎn)告訴你動(dòng)畫優(yōu)化的原理及其技巧,作為你工作中優(yōu)化動(dòng)畫的參考。瀏覽器渲染提高動(dòng)畫的優(yōu)化不得不提及瀏覽器是如何渲染一個(gè)頁(yè)面。
相信不少人在做移動(dòng)端動(dòng)畫的時(shí)候遇到了卡頓的問(wèn)題,這篇文章嘗試從瀏覽器渲染的角度;一點(diǎn)一點(diǎn)告訴你動(dòng)畫優(yōu)化的原理及其技巧,作為你工作中優(yōu)化動(dòng)畫的參考。文末有優(yōu)化技巧的總結(jié)。
因?yàn)镚PU合成沒有官方規(guī)范,每個(gè)瀏覽器的問(wèn)題和解決方式也不同;所以文章內(nèi)容僅供參考。
瀏覽器渲染提高動(dòng)畫的優(yōu)化不得不提及瀏覽器是如何渲染一個(gè)頁(yè)面。在從服務(wù)器中拿到數(shù)據(jù)后,瀏覽器會(huì)先做解析三類東西:
解析html,xhtml,svg這三類文檔,形成dom樹。
解析css,產(chǎn)生css rule tree。
解析js,js會(huì)通過(guò)api來(lái)操作dom tree和css rule tree。
解析完成之后,瀏覽器引擎會(huì)通過(guò)dom tree和css rule tree來(lái)構(gòu)建rendering tree:
rendering tree和dom tree并不完全相同,例如:
或display:none的東西就不會(huì)放在渲染樹中。css rule tree主要是完成匹配,并把css rule附加給rendering tree的每個(gè)element。
在渲染樹構(gòu)建完成后,
瀏覽器會(huì)對(duì)這些元素進(jìn)行定位和布局,這一步也叫做reflow或者layout。
瀏覽器繪制這些元素的樣式,顏色,背景,大小及邊框等,這一步也叫做repaint。
然后瀏覽器會(huì)將各層的信息發(fā)送給GPU,GPU會(huì)將各層合成;顯示在屏幕上。
渲染優(yōu)化原理如上所說(shuō),渲染樹構(gòu)建完成后;瀏覽器要做的步驟:
reflow——》repaint——》composite
reflow和repaintreflow和repaint都是耗費(fèi)瀏覽器性能的操作,這兩者尤以reflow為甚;因?yàn)槊看蝦eflow,瀏覽器都要重新計(jì)算每個(gè)元素的形狀和位置。
由于reflow和repaint都是非常消耗性能的,我們的瀏覽器為此做了一些優(yōu)化。瀏覽器會(huì)將reflow和repaint的操作積攢一批,然后做一次reflow。但是有些時(shí)候,你的代碼會(huì)強(qiáng)制瀏覽器做多次reflow。例如:
var content = document.getElementById("content"); content.style.width = 700px; var contentWidth = content.offsetWidth; content.style.backgound = "red";
以上第三行代碼,需要瀏覽器reflow后;再獲取值,所以會(huì)導(dǎo)致瀏覽器多做一次reflow。
下面是一些針對(duì)reflow和repaint的最佳實(shí)踐:
不要一條一條地修改dom的樣式,盡量使用className一次修改。
將dom離線后修改
使用documentFragment對(duì)象在內(nèi)存里操作dom。
先把dom節(jié)點(diǎn)display:none;(會(huì)觸發(fā)一次reflow)。然后做大量的修改后,再把它顯示出來(lái)。
clone一個(gè)dom節(jié)點(diǎn)在內(nèi)存里,修改之后;與在線的節(jié)點(diǎn)相替換。
不要使用table布局,一個(gè)小改動(dòng)會(huì)造成整個(gè)table的重新布局。
transform和opacity只會(huì)引起合成,不會(huì)引起布局和重繪。
從上述的最佳實(shí)踐中你可能發(fā)現(xiàn),動(dòng)畫優(yōu)化一般都是盡可能地減少reflow、repaint的發(fā)生。關(guān)于哪些屬性會(huì)引起reflow、repaint及composite,你可以在這個(gè)網(wǎng)站找到https://csstriggers.com/。
composite在reflow和repaint之后,瀏覽器會(huì)將多個(gè)復(fù)合層傳入GPU;進(jìn)行合成工作,那么合成是如何工作的呢?
假設(shè)我們的頁(yè)面中有A和B兩個(gè)元素,它們有absolute和z-index屬性;瀏覽器會(huì)重繪它們,然后將圖像發(fā)送給GPU;然后GPU將會(huì)把多個(gè)圖像合成展示在屏幕上。
AB
我們將A元素使用left屬性,做一個(gè)移動(dòng)動(dòng)畫:
AB
在這個(gè)例子中,對(duì)于動(dòng)畫的每一幀;瀏覽器會(huì)計(jì)算元素的幾何形狀,渲染新狀態(tài)的圖像;并把它們發(fā)送給GPU。(你沒看錯(cuò),position也會(huì)引起瀏覽器重排的)盡管瀏覽器做了優(yōu)化,在repaint時(shí),只會(huì)repaint部分區(qū)域;但是我們的動(dòng)畫仍然不夠流暢。
因?yàn)橹嘏藕椭乩L發(fā)生在動(dòng)畫的每一幀,一個(gè)有效避免reflow和repaint的方式是我們僅僅畫兩個(gè)圖像;一個(gè)是a元素,一個(gè)是b元素及整個(gè)頁(yè)面;我們將這兩張圖片發(fā)送給GPU,然后動(dòng)畫發(fā)生的時(shí)候;只做兩張圖片相對(duì)對(duì)方的平移。也就是說(shuō),僅僅合成緩存的圖片將會(huì)很快;這也是GPU的優(yōu)勢(shì)——它能非??斓匾詠喯袼鼐鹊睾铣蓤D片,并給動(dòng)畫帶來(lái)平滑的曲線。
為了僅發(fā)生composite,我們做動(dòng)畫的css property必須滿足以下三個(gè)條件:
不影響文檔流。
不依賴文檔流。
不會(huì)造成重繪。
滿足以上以上條件的css property只有transform和opacity。你可能以為position也滿足以上條件,但事實(shí)不是這樣,舉個(gè)例子left屬性可以使用百分比的值,依賴于它的offset parent。還有em、vh等其他單位也依賴于他們的環(huán)境。
我們使用translate來(lái)代替left
AB
瀏覽器在動(dòng)畫執(zhí)行之前就知道動(dòng)畫如何開始和結(jié)束,因?yàn)闉g覽器沒有看到需要reflow和repaint的操作;瀏覽器就會(huì)畫兩張圖像作為復(fù)合層,并將它們傳入GPU。
這樣做有兩個(gè)優(yōu)勢(shì):
動(dòng)畫將會(huì)非常流暢
動(dòng)畫不在綁定到CPU,即使js執(zhí)行大量的工作;動(dòng)畫依然流暢。
看起來(lái)性能問(wèn)題好像已經(jīng)解決了?在下文你會(huì)看到GPU動(dòng)畫的一些問(wèn)題。
GPU是如何合成圖像的GPU實(shí)際上可以看作一個(gè)獨(dú)立的計(jì)算機(jī),它有自己的處理器和存儲(chǔ)器及數(shù)據(jù)處理模型。當(dāng)瀏覽器向GPU發(fā)送消息的時(shí)候,就像向一個(gè)外部設(shè)備發(fā)送消息。
你可以把瀏覽器向GPU發(fā)送數(shù)據(jù)的過(guò)程,與使用ajax向服務(wù)器發(fā)送消息非常類似。想一下,你用ajax向服務(wù)器發(fā)送數(shù)據(jù),服務(wù)器是不會(huì)直接接受瀏覽器的存儲(chǔ)的信息的。你需要收集頁(yè)面上的數(shù)據(jù),把它們放進(jìn)一個(gè)載體里面(例如JSON),然后發(fā)送數(shù)據(jù)到遠(yuǎn)程服務(wù)器。
同樣的,瀏覽器向GPU發(fā)送數(shù)據(jù)也需要先創(chuàng)建一個(gè)載體;只不過(guò)GPU距離CPU很近,不會(huì)像遠(yuǎn)程服務(wù)器那樣可能幾千里那么遠(yuǎn)。但是對(duì)于遠(yuǎn)程服務(wù)器,2秒的延遲是可以接受的;但是對(duì)于GPU,幾毫秒的延遲都會(huì)造成動(dòng)畫的卡頓。
瀏覽器向GPU發(fā)送的數(shù)據(jù)載體是什么樣?這里給出一個(gè)簡(jiǎn)單的制作載體,并把它們發(fā)送到GPU的過(guò)程。
畫每個(gè)復(fù)合層的圖像
準(zhǔn)備圖層的數(shù)據(jù)
準(zhǔn)備動(dòng)畫的著色器(如果需要)
向GPU發(fā)送數(shù)據(jù)
所以你可以看到,每次當(dāng)你添加transform:translateZ(0)或will-change:transform給一個(gè)元素,你都會(huì)做同樣的工作。重繪是非常消耗性能的,在這里它尤其緩慢。在大多數(shù)情況,瀏覽器不能增量重繪。它不得不重繪先前被復(fù)合層覆蓋的區(qū)域。
隱式合成還記得剛才a元素和b元素動(dòng)畫的例子嗎?現(xiàn)在我們將b元素做動(dòng)畫,a元素靜止不動(dòng)。
和剛才的例子不同,現(xiàn)在b元素將擁有一個(gè)獨(dú)立復(fù)合層;然后它們將被GPU合成。但是因?yàn)閍元素要在b元素的上面(因?yàn)閍元素的z-index比b元素高),那么瀏覽器會(huì)做什么?瀏覽器會(huì)將a元素也多帶帶做一個(gè)復(fù)合層!
所以我們現(xiàn)在有三個(gè)復(fù)合層a元素所在的復(fù)合層、b元素所在的復(fù)合層、其他內(nèi)容及背景層。
一個(gè)或多個(gè)沒有自己復(fù)合層的元素要出現(xiàn)在有復(fù)合層元素的上方,它就會(huì)擁有自己的復(fù)合層;這種情況被稱為隱式合成。
瀏覽器將a元素提升為一個(gè)復(fù)合層有很多種原因,下面列舉了一些:
3d或透視變換css屬性,例如translate3d,translateZ等等(js一般通過(guò)這種方式,使元素獲得復(fù)合層)
混合插件(如flash)。
元素自身的 opacity和transform 做 CSS 動(dòng)畫。
擁有css過(guò)濾器的元素。
使用will-change屬性。
position:fixed。
元素有一個(gè) z-index 較低且包含一個(gè)復(fù)合層的兄弟元素(換句話說(shuō)就是該元素在復(fù)合層上面渲染)
這看起來(lái)css動(dòng)畫的性能瓶頸是在重繪上,但是真實(shí)的問(wèn)題是在內(nèi)存上:
內(nèi)存占用使用GPU動(dòng)畫需要發(fā)送多張渲染層的圖像給GPU,GPU也需要緩存它們以便于后續(xù)動(dòng)畫的使用。
一個(gè)渲染層,需要多少內(nèi)存占用?為了便于理解,舉一個(gè)簡(jiǎn)單的例子;一個(gè)寬、高都是300px的純色圖像需要多少內(nèi)存?
300 300 4 = 360000字節(jié),即360kb。這里乘以4是因?yàn)?,每個(gè)像素需要四個(gè)字節(jié)計(jì)算機(jī)內(nèi)存來(lái)描述。
假設(shè)我們做一個(gè)輪播圖組件,輪播圖有10張圖片;為了實(shí)現(xiàn)圖片間平滑過(guò)渡的交互;為每個(gè)圖像添加了will-change:transform。這將提升圖像為復(fù)合層,它將多需要19mb的空間。800 600 4 * 10 = 1920000。
僅僅是一個(gè)輪播圖組件就需要19m的額外空間!
在chrome的開發(fā)者工具中打開setting——》Experiments——》layers可以看到每個(gè)層的內(nèi)存占用。如圖所示:
GPU動(dòng)畫的優(yōu)點(diǎn)和缺點(diǎn)現(xiàn)在我們可以總結(jié)一下GPU動(dòng)畫的優(yōu)點(diǎn)和缺點(diǎn):
每秒60幀,動(dòng)畫平滑、流暢。
一個(gè)合適的動(dòng)畫工作在一個(gè)多帶帶的線程,它不會(huì)被大量的js計(jì)算阻塞。
3D“變換”是便宜的。
缺點(diǎn):
提升一個(gè)元素到復(fù)合層需要額外的重繪,有時(shí)這是慢的。(即我們得到的是一個(gè)全層重繪,而不是一個(gè)增量)
繪圖層必須傳輸?shù)紾PU。取決于層的數(shù)量和傳輸可能會(huì)非常緩慢。這可能讓一個(gè)元素在中低檔設(shè)備上閃爍。
每個(gè)復(fù)合層都需要消耗額外的內(nèi)存,過(guò)多的內(nèi)存可能導(dǎo)致瀏覽器的崩潰。
如果你不考慮隱式合成,而使用重繪;會(huì)導(dǎo)致額外的內(nèi)存占用,并且瀏覽器崩潰的概率是非常高的。
我們會(huì)有視覺假象,例如在Safari中的文本渲染,在某些情況下頁(yè)面內(nèi)容將消失或變形。
優(yōu)化技巧 避免隱式合成保持動(dòng)畫的對(duì)象的z-index盡可能的高。理想的,這些元素應(yīng)該是body元素的直接子元素。當(dāng)然,這不是總可能的。所以你可以克隆一個(gè)元素,把它放在body元素下僅僅是為了做動(dòng)畫。
將元素上設(shè)置will-change CSS屬性,元素上有了這個(gè)屬性,瀏覽器會(huì)提升這個(gè)元素成為一個(gè)復(fù)合層(不是總是)。這樣動(dòng)畫就可以平滑的開始和結(jié)束。但是不要濫用這個(gè)屬性,否則會(huì)大大增加內(nèi)存消耗。
動(dòng)畫中只使用transform和opacity如上所說(shuō),transform和opacity保證了元素屬性的變化不影響文檔流、也不受文檔流影響;并且不會(huì)造成repaint。
有些時(shí)候你可能想要改變其他的css屬性,作為動(dòng)畫。例如:你可能想使用background屬性改變背景:
.bg-change { width: 100px; height: 100px; background: red; transition: opacity 2s; } .bg-change:hover { background: blue; }
在這個(gè)例子中,在動(dòng)畫的每一步;瀏覽器都會(huì)進(jìn)行一次重繪。我們可以使用一個(gè)復(fù)層在這個(gè)元素上面,并且僅僅變換opacity屬性:
減小復(fù)合層的尺寸
看一下兩張圖片,有什么不同嗎?
這兩張圖片視覺上是一樣的,但是它們的尺寸一個(gè)是39kb;另外一個(gè)是400b。不同之處在于,第二個(gè)純色層是通過(guò)scale放大10倍做到的。
對(duì)于圖片,你要怎么做呢?你可以將圖片的尺寸減少5%——10%,然后使用scale將它們放大;用戶不會(huì)看到什么區(qū)別,但是你可以減少大量的存儲(chǔ)空間。
用css動(dòng)畫而不是js動(dòng)畫css動(dòng)畫有一個(gè)重要的特性,它是完全工作在GPU上。因?yàn)槟懵暶髁艘粋€(gè)動(dòng)畫如何開始和如何結(jié)束,瀏覽器會(huì)在動(dòng)畫開始前準(zhǔn)備好所有需要的指令;并把它們發(fā)送給GPU。而如果使用js動(dòng)畫,瀏覽器必須計(jì)算每一幀的狀態(tài);為了保證平滑的動(dòng)畫,我們必須在瀏覽器主線程計(jì)算新狀態(tài);把它們發(fā)送給GPU至少60次每秒。除了計(jì)算和發(fā)送數(shù)據(jù)比css動(dòng)畫要慢,主線程的負(fù)載也會(huì)影響動(dòng)畫; 當(dāng)主線程的計(jì)算任務(wù)過(guò)多時(shí),會(huì)造成動(dòng)畫的延遲、卡頓。
所以盡可能地使用基于css的動(dòng)畫,不僅僅更快;也不會(huì)被大量的js計(jì)算所阻塞。
優(yōu)化技巧總結(jié)減少瀏覽器的重排和重繪的發(fā)生。
不要使用table布局。
css動(dòng)畫中盡量只使用transform和opacity,這不會(huì)發(fā)生重排和重繪。
盡可能地只使用css做動(dòng)畫。
避免瀏覽器的隱式合成。
改變復(fù)合層的尺寸。
參考GPU合成主要參考:
https://www.smashingmagazine....
哪些屬性會(huì)引起reflow、repaint及composite,你可以在這個(gè)網(wǎng)站找到:
https://csstriggers.com/。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81099.html
摘要:相信不少人在做移動(dòng)端動(dòng)畫的時(shí)候遇到了卡頓的問(wèn)題,這篇文章嘗試從瀏覽器渲染的角度一點(diǎn)一點(diǎn)告訴你動(dòng)畫優(yōu)化的原理及其技巧,作為你工作中優(yōu)化動(dòng)畫的參考。瀏覽器渲染提高動(dòng)畫的優(yōu)化不得不提及瀏覽器是如何渲染一個(gè)頁(yè)面。 相信不少人在做移動(dòng)端動(dòng)畫的時(shí)候遇到了卡頓的問(wèn)題,這篇文章嘗試從瀏覽器渲染的角度;一點(diǎn)一點(diǎn)告訴你動(dòng)畫優(yōu)化的原理及其技巧,作為你工作中優(yōu)化動(dòng)畫的參考。文末有優(yōu)化技巧的總結(jié)。 因?yàn)镚PU合...
摘要:如果你看到我的前一篇文章一篇文章說(shuō)清瀏覽器解析和動(dòng)畫優(yōu)化,理解本篇文章還是很簡(jiǎn)單的。,元素的終止?fàn)顟B(tài)。這會(huì)打斷正在運(yùn)行的動(dòng)畫,這是糟糕的。關(guān)鍵是確保你的預(yù)計(jì)算在用戶響應(yīng)的空閑時(shí)間執(zhí)行,這樣兩個(gè)動(dòng)畫就不會(huì)沖突了。 在vue官方文檔上看到vue使用flip做動(dòng)畫,就去研究了一下。這是一個(gè)準(zhǔn)則,協(xié)調(diào)js和css對(duì)動(dòng)畫的操作。如果你看到我的前一篇文章一篇文章說(shuō)清瀏覽器解析和CSS(GPU)動(dòng)畫...
摘要:如果你看到我的前一篇文章一篇文章說(shuō)清瀏覽器解析和動(dòng)畫優(yōu)化,理解本篇文章還是很簡(jiǎn)單的。,元素的終止?fàn)顟B(tài)。這會(huì)打斷正在運(yùn)行的動(dòng)畫,這是糟糕的。關(guān)鍵是確保你的預(yù)計(jì)算在用戶響應(yīng)的空閑時(shí)間執(zhí)行,這樣兩個(gè)動(dòng)畫就不會(huì)沖突了。 在vue官方文檔上看到vue使用flip做動(dòng)畫,就去研究了一下。這是一個(gè)準(zhǔn)則,協(xié)調(diào)js和css對(duì)動(dòng)畫的操作。如果你看到我的前一篇文章一篇文章說(shuō)清瀏覽器解析和CSS(GPU)動(dòng)畫...
摘要:渲染層合并對(duì)頁(yè)面中元素的繪制是在多個(gè)層上進(jìn)行的。擁有兩套不同的渲染路徑硬件加速路徑和舊軟件路徑中有不同類型的層負(fù)責(zé)子樹和負(fù)責(zé)的子樹,只有是作為紋理上傳給的。整個(gè)圖在中其實(shí)有幾種不同的層類型渲染層,這是負(fù)責(zé)對(duì)應(yīng)子樹圖形層,這是負(fù)責(zé)對(duì)應(yīng)子樹。 梳理瀏覽器渲染流程 首先簡(jiǎn)單了解一下瀏覽器請(qǐng)求、加載、渲染一個(gè)頁(yè)面的大致過(guò)程: DNS 查詢 TCP 連接 HTTP 請(qǐng)求即響應(yīng) 服務(wù)器響應(yīng) 客戶...
摘要:渲染層合并對(duì)頁(yè)面中元素的繪制是在多個(gè)層上進(jìn)行的。擁有兩套不同的渲染路徑硬件加速路徑和舊軟件路徑中有不同類型的層負(fù)責(zé)子樹和負(fù)責(zé)的子樹,只有是作為紋理上傳給的。整個(gè)圖在中其實(shí)有幾種不同的層類型渲染層,這是負(fù)責(zé)對(duì)應(yīng)子樹圖形層,這是負(fù)責(zé)對(duì)應(yīng)子樹。 梳理瀏覽器渲染流程 首先簡(jiǎn)單了解一下瀏覽器請(qǐng)求、加載、渲染一個(gè)頁(yè)面的大致過(guò)程: DNS 查詢 TCP 連接 HTTP 請(qǐng)求即響應(yīng) 服務(wù)器響應(yīng) 客戶...
閱讀 654·2021-10-13 09:39
閱讀 1461·2021-09-09 11:53
閱讀 2657·2019-08-29 13:55
閱讀 731·2019-08-28 18:08
閱讀 2600·2019-08-26 13:54
閱讀 2415·2019-08-26 11:44
閱讀 1844·2019-08-26 11:41
閱讀 3796·2019-08-26 10:15