onLoad 監聽頁面載入
onReady 監聽頁面初次渲染完成
onShow 監聽頁面顯示
到底是什麼意思?
所以這又觸碰到了我的知識盲區,不過專案在磕磕絆絆中完成的差不多了,但是遇到了CSS3動畫渲染的效能問題,所以我也是被逼的,再回過頭來從瀏覽器渲染網頁的流程出發,去找動畫卡頓的癥結。
瀏覽器渲染網頁的流程如下:
使用 HTML 建立文件物件模型(DOM)
使用 CSS 建立 CSS 物件模型(CSSOM)
基於 DOM 和 CSSOM 執行指令碼(Scripts)
合併 DOM 和 CSSOM 形成渲染樹(Render Tree)
使用渲染樹佈局(Layout)所有元素
渲染(Paint)所有元素
可以結合Alon的這篇前端效能優化和安卓開發者選項的顯示頁面佈局。
安卓開發者選項的顯示頁面佈局
如何判斷手機app是native,webview還是hybird?
簡單說下,app中的一大塊是白色的沒有紅線標記出來的,但是上面有按鈕,圖片等時,就是webview,也就是通過一個偽瀏覽器去請求到的資料,斷網時開啟app沒有任何東西顯示在上面
onLoad 監聽頁面載入
在渲染完介面之後,也就是通過.json中的配置項生成native介面後,開始渲染webview的部分,一個頁面只會呼叫一次。
onReady 監聽頁面初次渲染完成
一個頁面只會呼叫一次,代表頁面已經準備妥當,可以和檢視層進行互動。
onShow 監聽頁面顯示
每次開啟頁面都會去呼叫其中的函式。
我們的動畫應該放在哪裡?
應該放在onShow裡,因為這樣我每次開啟都能看到動畫。
為什麼會卡頓?
有一個前提必須要提,前端開發者們都知道,瀏覽器是單執行緒執行的。
但是我們要明確以下幾個概念:單執行緒,主執行緒和合成執行緒。
雖然說瀏覽器執行js是單執行緒執行(注意,是執行,並不是說瀏覽器只有1個執行緒,而是執行時,runing),但實際上瀏覽器的2個重要的執行執行緒,這 2 個執行緒協同工作來渲染一個網頁:主執行緒和合成執行緒。
一般情況下,主執行緒負責:執行 JavaScript;計算 HTML 元素的 CSS 樣式;頁面的佈局;將元素繪製到一個或多個點陣圖中;將這些點陣圖交給合成執行緒。
相應地,合成執行緒負責:通過 GPU 將點陣圖繪製到螢幕上;通知主執行緒更新頁面中可見或即將變成可見的部分的點陣圖;計算出頁面中哪部分是可見的;計算出當你在滾動頁面時哪部分是即將變成可見的;當你滾動頁面時將相應位置的元素移動到可視區域。
那麼為什麼會造成動畫卡頓呢?
原因就是主執行緒和合成執行緒的排程不合理。
下面來詳細說一下排程不合理的原因。
在使用height,width,margin,padding作為transition的值時,會造成瀏覽器主執行緒的工作量較重,例如從margin-left:-20px渲染到margin-left:0,主執行緒需要計算樣式margin-left:-19px,margin-left:-18px,一直到margin-left:0,而且每一次主執行緒計算樣式後,合成程式都需要繪製到GPU然後再渲染到螢幕上,前後總共進行20次主執行緒渲染,20次合成執行緒渲染,20+20次,總計40次計算。
主執行緒的渲染流程,可以參考瀏覽器渲染網頁的流程:
使用 HTML 建立文件物件模型(DOM)
使用 CSS 建立 CSS 物件模型(CSSOM)
**基於 DOM 和 CSSOM 執行指令碼(Scripts)
合併 DOM 和 CSSOM 形成渲染樹(Render Tree)
使用渲染樹佈局(Layout)所有元素
渲染(Paint)所有元素**
也就是說,主執行緒每次都需要執行Scripts,Render Tree ,Layout和Paint這四個階段的計算。
而如果使用transform的話,例如tranform:translate(-20px,0)到transform:translate(0,0),主執行緒只需要進行一次tranform:translate(-20px,0)到transform:translate(0,0),然後合成執行緒去一次將-20px轉換到0px,這樣的話,總計1+20計算。
可能會有人說,這才提升了19次,有什麼好效能提升的?
假設一次10ms。
那麼就減少了約190ms的耗時。
會有人說,辣雞,才190ms,無所謂。
那麼如果margin-left是從-200px到0呢,一次10ms,10ms*199≈2s。
還會有人說,辣雞,也就2s,無所謂。
你忘了單執行緒這回事了嗎?
如果網頁有3個動畫,3*2s=6s,就是6s的效能提升。
由於資料是猜測的,所以暫時不考慮其真實性,文章後面我使用chrome devtools的performance做了一個實驗。
要知道,在”客戶至上”的今天,好的使用者體驗是所有產品的必須遵守的一條規則,無論是對於開發者還是產品經理,追求極致的效能都是我們打造一個好的產品所必備的品質。
可能看了我的略不專業的分析後,大家對主執行緒,合成執行緒以及它們在2種效能不同動畫方案上的工作流程還不是很瞭解,可以去看一篇翻譯過來的部落格(英文原版連結已經失效了):深入瀏覽器理解CSS animations 和 transitions的效能問題
這篇文章完美講述了瀏覽器主執行緒和合成執行緒的區別,並且舉了一個高度從100px變化到200px的2種動畫方案的對比,對主執行緒和合成執行緒的整個工作流程做了很詳盡的講解,真心建議認真閱讀一遍。
回過頭來總結下,css3動畫卡頓的解決方案:
在使用css3 transtion做動畫效果時,優先選擇transform,儘量不要使用height,width,margin和padding。
transform為我們提供了豐富的api,例如scale,translate,rotate等等,但是在使用時需要考慮相容性。但其實對於大多數css3來說,mobile端支援性較好,desktop端支援性需要格外注意。
補充:為了增強本文的說服力,特地回家做了一個實驗,程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Page Title</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .margin-transition{ /* margin-left: 0; */ background: rgba(0,0,255,0.3); transition: margin-left 1s; } .transform-transition{ /* transform: translate(0,0); */ background: rgba(0,255,0,0.3); transition: transform 1s; } .common{ height: 300px; width: 300px; } </style> </head> <body> <div class="margin-transition common" id="marginTransition"> <p>transition:margin-left 1s</p> </div> <div class="transform-transition common" id="transformTransition"> <p>transition:tranform 1s</p> </div> <button id="control">見證奇蹟</button> <script> var btn = document.getElementById('control'); var marginTransition = document.getElementById('marginTransition'); var transformTransition = document.getElementById('transformTransition'); btn.addEventListener("click",function(){ console.log(marginTransition.style,transformTransition.style) marginTransition.style.marginLeft = "500px"; transformTransition.style.transform = "translate(500px,0)" }) </script> </body> </html> |
我將主要藉助chrome devtools的performance工具對比二者的效能差異。
先來看margin動畫,動態修改DOM節點的margin-left值從0到500px;。
1 |
transition: margin-left 1s; |
再來看下transform動畫,動態修改DOM節點的transform值從translate(0,0)到translate(500px,0)。
1 |
transition: transform 1s; |
可能圖片不是很好地能說明效能差異,那麼我們來列一張耗時對比表,方便我們計算。
耗時 | margin | transform |
---|---|---|
Summery | 3518ms | 2286ms |
Scripting | 1.8ms | 2.9ms |
Rendering | 22.5ms | 6.9ms |
Painting | 9.7ms | 1.6ms |
Other | 39.3ms | 25.2ms |
Idle( browser is waiting on the CPU or GPU to do some processing) | 3444.4ms | 2249.8ms |
GPU使用率 | 4.1MB | 1.7MB |
通過上表我們可以計算出明margin,transform與transition組合實現CSS3動畫效果時的效能差異引數。
關鍵效能引數 | margin | transform |
---|---|---|
實際動畫耗時(總時間 減去 空閒時間) | 73.6ms | 36.2ms |
計算得出,transform動畫耗時約等於margin動畫耗時的0.49倍,效能優化50%。
由於我對Other的所做的具體事情不是很清楚,所以這裡的實際動畫時間也有可能還要減掉Other中的時間,下表是我們減掉後的資料。
關鍵效能引數 | margin | transform |
---|---|---|
實際動畫耗時(總時間 減去 其他時間和空閒時間) | 34.3ms | 11ms |
計算得出,transform動畫耗時約等於margin動畫耗時的0.32倍,效能優化接近70%。
也就是說,無論我們減去還是不減去Other的時間,我們採用transform實現動畫的方式都比margin動畫快。
不精確的得出一個小結論:transform比margin效能好50%~70%。
雖然會有50%~70%的效能提升,但是需要注意硬體差異,硬體好的情況下可能不能發現卡頓或者其他的一些效能上的問題。
例如在開發小程式的過程中,模擬器是位於desktop端的,因此它的硬體效能效能更好,例如CPU,GPU。但是一旦在mobile端執行,例如ios或者android上執行時,就可能會出現效能問題,這就是因為移動端的硬體條件遜於PC端導致的。
所以說,效能問題是一直存在的,只不過硬體差異會導致效能影響的程度不同。
所以我們再次回過頭來,總結出css3動畫卡頓的解決方案:
在使用css3 transtion做動畫效果時,優先選擇transform,儘量不要使用height,width,margin和padding。
That’s it !
參考:
http://sy-tang.github.io/2014…
http://jinlong.github.io/2017…
http://blog.csdn.net/yeana1/a…
https://www.jianshu.com/p/b70…
https://developers.google.com…
http://blogs.adobe.com/webpla…