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