大多數設備的刷新頻率是60次/秒,也就是1秒鐘的動畫是由60個畫面連在一塊兒生成的,因此要求瀏覽器對每一幀畫面的渲染工做要在16ms內完成。當渲染時間超出16ms時,1秒鐘內少於60個畫面生成,就會有不連貫、卡頓的感受,影響用戶體驗。css
一個頁面幀在客戶端的渲染分爲如下幾步:
web
JavaScript
:JavaScript實現動畫效果,DOM操做等。Style(樣式計算)
:確認每一個DOM元素應用的CSS樣式規則。Layout(佈局)
:計算每一個DOM元素最終在屏幕上的大小和位置。因爲DOM元素的佈局是相對的,因此當某個元素髮生變化影響了佈局時,其餘元素也會隨之變化,則須要回退從新渲染,這個過程稱之爲reflow。Paint(繪製)
:在多個層上繪製DOM元素的文字、顏色、圖像、邊框和陰影等。Composite(Render Layer合併)
:按照合理的順序合併圖層並顯示到屏幕上。 瀏覽器在實際渲染頁面的時候須要通過一系列的映射,由HTML頁面構建出來的DOM樹到最終的圖層,映射過程以下圖(來源:參考[3])所示(注意下圖類名在後續有所更改,RenderObject->LayoutObject,RenderLayer->PaintLayer):Node->RenderObject:DOM樹的每一個Node都有一個對應的RenderObject(一對一關係,RenderObject包含了Node的內容);ajax
RenderObject -> RenderLayer:一個或多個RenderObject對應一個RenderLayer(多對一),RenderLayer用於保證元素之間的層級關係,通常來講位於同一位置的且層級相同的元素位於同一個Render Layer,只有某些特殊的RenderObject會專門建立一個新的渲染層,其餘的RenderObject與第一個擁有RenderLayer的祖先元素共用一個。常見的生成RenderLayer的RenderObject擁有如下的一種特徵參考[3]:chrome
RenderLayer -> GraphicsLayer:一個或多個RenderLayer對應一個GraphicsLayer(多對一),某些被認爲是Compositing Layer的RenderLayer單獨對應一個GraphicsLayer,其餘RenderLayer與第一個擁有GraphicsLayer的祖先元素共用一個GraphicsLayer。每一個GraphicsLayer有一個GraphicsContext用於繪製其對應的RenderLayers,合成器將GraphicsContexts的位圖合成,最終顯示到屏幕上。渲染層提高爲合成層的緣由以下:canvas
在網上能夠看到不少的優化方案總結,大佬們都寫的很好。windows
Talk is cheap. Show me the code.瀏覽器
結合頁面渲染流程,這裏將結合一些測試代碼,分析動畫的各類優化方案和效果:性能優化
JavaScript
:優化JavaScript的執行效率
requestAnimationFrame
代替setTimeout
和setInterval
Web Workers
中Style
:下降樣式計算複雜度和範圍
Layout
:避免大規模、複雜的佈局
Paint/Composite
:GPU加速
JavaScript
:優化JavaScript的執行效率requestAnimationFrame
代替setTimeout
和setInterval
爲何setTimeout
和setInterval
很差?
因爲js是單線程執行,因此爲了防止某個任務執行時間過長而致使進程阻塞,js中存在異步隊列的概念,對於如setTimeout
和ajax
請求都是把進程放到了異步隊列中,當主進程爲空時才執行異步隊列中的任務。因此 setTimeout
和setInterval
沒法保證回調函數的執行時機,可能會在一幀以內執行屢次致使屢次頁面渲染,浪費CPU資源甚至產生卡頓,或者是在一幀即將結束時執行致使從新渲染,出現掉幀的狀況。
requestAnimationFrame
是怎麼優化的?服務器
優化效果具體如何?DEMO
經過chrome的performance面板查看具體表現的差異。
經過setTimeout
進行了3次渲染,並且有長時間幀出現:
網絡
requestAnimationFrame
DOM操做部分合並,只進行了2次渲染,長時間幀也被優化:
Web Worker
中Web Worker
的好處是什麼?
JavaScript是單線程的,若是頻繁的進行耗時操做(如實時更新數據),就會形成擁堵,影響用戶交互體驗。Web Worker
的做用在於爲JavaScript建立了多線程環境,worker線程在後臺運行,受主線程控制,二者互不干擾。worker線程負擔高延遲且UI無關的任務,主線程負責UI交互就會相對流暢。
須要注意
Web Worker
沒法操做DOM,本質上只是將數據刷新和頁面渲染拆開執行。Web Worker
遵循同源策略且限制本地訪問。優化效果具體如何?DEMO
能夠經過chrome的performance面板查看具體表現的差異: 不使用web worker
,減小了一次網絡請求,可是出現了長時間幀,有卡幀的風險。
web worker
以後,耗時操做無關的任務再也不被阻塞,可是增長了網絡延遲。若是在項目中使用worker,初始化時間須要好好斟酌。
可考慮的應用場景
Style
:下降樣式計算複雜度和範圍下降樣式選擇器的複雜度是經常被提出的一個優化方法,實際上這個方法的效果比較微弱,根據Ivan Curic的文章[5]的測試方法(DEMO),在一個擁有50000個節點的頁面中,不一樣選擇器複雜度對於性能的影響不會超過20ms,而通常狀況下,頁面的節點數都不會達到這個數量。
優化效果微弱的緣由在於瀏覽器引擎對選擇器速度進行了優化,不一樣引擎的性能優化方案不一樣,因此開發者的優化是否有效是難以預測的,至少對於靜態元素的優化性價比是極低的。
經過測試能夠確認的一點是,應當減小僞類選擇器和過長的選擇器的使用。推薦按照如OOCSS、BEM等命名規範來組織CSS,優勢是在微弱優化性能的同時也提升了代碼可維護性。
這一點是針對較早的瀏覽器而言,較早的瀏覽器如改變了body
元素上的一個類,則其子元素都須要從新計算樣式。
現代瀏覽器都進行了優化,因此優化效果要視具體應用場景而言。目前還沒有挖掘到應用例子,後期若有發現回來填坑。
Layout
:避免大規模、複雜的佈局不一樣的屬性致使的渲染成本不盡相同,這一點在css動畫時對比尤爲明顯。觸發layout或者paint的動畫屬性尤爲消耗性能,因此應當儘可能使用transform
和opacity
做爲動畫屬性,若是沒法實現則考慮採用JavaScript實現動畫。
性能差異有多大? 以width和transform爲例,分別實現動畫的性能差異:DEMO
經過width實現動畫,幀率較低且曲線抖動明顯,右下角也給出了一幀的渲染過程,觸發了樣式計算,佈局,繪製和渲染層合併:
經常使用的經典佈局方案有基於浮動的佈局、基於絕對定位的佈局,flexbox佈局相較而言更加高效。在能用flexbox佈局的項目中,儘可能用flexbox佈局。如下DEMO嘗試用三種佈局方式渲染同樣的界面效果來測試性能:
絕對佈局:對於每個元素都須要惟一的定位座標,當元素較多時,CSS文件偏大,致使在樣式計算上花費了較多的時間。
什麼是強制同步佈局?
前面提到了頁面渲染流程是JavaScript->Style->Layout->Paint->Composite,強制同步佈局就是強制瀏覽器在執行JavaScript腳本前先執行佈局。
什麼狀況會致使強制同步佈局?
JavaScript運行時,獲取到的元素屬性樣式都是上一幀的數值,因此若是在當前幀的渲染流程中,獲取當前幀的某個元素屬性以前對該元素進行了修改,瀏覽器就必須先應用屬性再執行JavaScript邏輯,簡而言之就是DOM先寫後讀操做,尤爲是連續的讀寫操做,對瀏覽器的性能影響更大。 對性能影響有多大?DEMO
DEMO經過改變1000個節點的屬性,測試強制同步佈局事件對性能的影響,具體參照下圖。能夠發現性能的損耗是極大的,連續的讀寫操做致使連續的強制同步事件觸發,JavaScript執行時間變得很長:
Paint/Composite
:GPU加速注:可在Chrome的開發者工具的layers面板查看合成層,layers面板打開方法command+shift+p(mac)/ctrl+shift+p(windows) -> show layers 將複雜/頻繁變化的元素提高到合成層,這樣的好處是該元素繪製的時候不會觸發其餘元素的繪製。渲染層提高爲合成層的緣由以下(注意如下緣由是在渲染層的基礎之上):
爲何會有性能提高?
性能提高有多少? DEMO 經過demo能夠看到,提高爲合成層以後,paint所需的時間大大減小。
提高合成層是否是越多越好?
能夠看到提高合成層後,paint時間大大降低。可是合成層的建立須要消耗額外的內存和管理資源,過多的合成層給頁面帶來的內存開銷很大,DEMO建立了5000個元素,所有元素都提高爲合成層與不提高時的內存消耗進行對比。這一點在移動端尤爲須要注意,相比較於PC,移動設備的內存資源更加緊張。
只提高動畫元素的渲染層
基於提高爲合成層來提高性能的原理,當頁面其餘部分繪製比較複雜且相對靜態時,咱們能夠考慮將動畫元素單獨提高爲合成層,減小動畫元素對頁面其餘元素的影響。
回顧一下提高爲合成層的最後一個緣由:兄弟元素是compositing layer,與當前的非composting layer有重疊,composting layer的層級低於非composting layer層。
這種狀況下致使的提高合成層通常都是預期外的。其緣由與屏幕的渲染流程有關,咱們回憶一下頁面映射的最後一步,每個Compositing Layer對應一張位圖,合成器最後將這些位圖根據層級關係合併起來最終輸出到屏幕。此時咱們假設A是已知的合成層,而B理想中應當是普通渲染層,其層級關係如圖所示:
scrollsWithRespectToSquashingLayer
:渲染層相對於壓縮層滾動,當滾動的渲染層與合成層重疊時,會有新的合成層生成且沒法壓縮。DEMO(這個例子不是很好,codepen用iframe嵌入,整個iframe都變成了合成層,若是想看效果能夠在本地看)squashingSparsityExceeded
:渲染層壓縮後會致使壓縮層過於稀疏。DEMOsquashingClippingContainerMismatch
:渲染層和壓縮層的裁剪容器(clip container)不一樣,簡單理解就是重疊的渲染層的容器overflow類型不一樣。DEMOsquashingOpacityAncestorMismatch
:渲染層與壓縮層的繼承自祖先的opacity屬性不一樣。DEMOsquashingTransformAncestorMismatch
:渲染層與壓縮層的繼承自祖先的transform不一樣。DEMOsquashingFilterAncestorMismatch
:渲染層與壓縮層的繼承自祖先的filter屬性不一樣,或者是渲染層自己有filter屬性。DEMOsquashingWouldBreakPaintOrder
:沒法在不打亂渲染順序的前提下壓縮(e.g. 父元素有mask/filter屬性,子元素與壓縮層overlap,則假如合併了,父元素的mask/filter屬性沒法局部應用在壓縮層,致使渲染結果有誤)。DEMOsquashingVideoIsDisallowed
:video元素沒法被壓縮。DEMOsquashedLayerClipsCompositingDescendants
:當合成層是被剪切的子元素時,與之重疊的渲染層沒法被壓縮。DEMOsquashingLayoutPartIsDisallowed
:沒法壓縮frame/iframe/plugin。squashingReflectionDisallowed
:沒法壓縮有reflection屬性的渲染層。 DEMOsquashingBlendingDisallowed
:沒法壓縮有blend mode屬性的渲染層。DEMOsquashingNearestFixedPositionMismatch
:渲染層的最近fixed元素與壓縮層不一樣,沒法被壓縮。DEMO當發現頁面明明沒有什麼內容卻比較卡的時候能夠檢查一下是否是這個緣由,如下給出常見的層壓縮解決不了的狀況:
transform
動畫的元素,其後的元素爲relative/absolute
定位squashingClippingContainerMismatch
,渲染層與合成層的裁剪容器不一樣,致使沒法層壓縮,出現過多的合成層。 解決方法:爲動畫的元素設置z-index
擾亂compositing layer的排序。DEMO本文結構主要參照文章[1],對其中的一些優化點進行了實際測試和擴展,也算是一篇讀後感吧~
關於層壓縮部分狀況過於複雜,沒找到什麼資料,感受尚未徹底吃透,後面有機會再從新整理一下。感恩如下大佬!
撒花完結~歡迎指教~