優化動畫卡頓:卡頓緣由分析及優化方案

目錄

1、動畫卡頓分析

動畫卡頓的緣由

大多數設備的刷新頻率是60次/秒,也就是1秒鐘的動畫是由60個畫面連在一塊兒生成的,因此要求瀏覽器對每一幀畫面的渲染工做要在16ms內完成。當渲染時間超出16ms時,1秒鐘內少於60個畫面生成,就會有不連貫、卡頓的感受,影響用戶體驗。css

頁面渲染流程

一個頁面幀在客戶端的渲染分爲如下幾步:
web

頁面渲染流程 來源:Google

  1. JavaScript:JavaScript實現動畫效果,DOM操做等。
  2. Style(樣式計算):確認每一個DOM元素應用的CSS樣式規則。
  3. Layout(佈局):計算每一個DOM元素最終在屏幕上的大小和位置。因爲DOM元素的佈局是相對的,因此當某個元素髮生變化影響了佈局時,其餘元素也會隨之變化,則須要回退從新渲染,這個過程稱之爲reflow。
  4. Paint(繪製):在多個層上繪製DOM元素的文字、顏色、圖像、邊框和陰影等。
  5. Composite(Render Layer合併):按照合理的順序合併圖層並顯示到屏幕上。 瀏覽器在實際渲染頁面的時候須要通過一系列的映射,由HTML頁面構建出來的DOM樹到最終的圖層,映射過程以下圖(來源:參考[3])所示(注意下圖類名在後續有所更改,RenderObject->LayoutObject,RenderLayer->PaintLayer):
    The Compositing Tree
  • Node->RenderObject:DOM樹的每一個Node都有一個對應的RenderObject(一對一關係,RenderObject包含了Node的內容);ajax

  • RenderObject -> RenderLayer:一個或多個RenderObject對應一個RenderLayer(多對一),RenderLayer用於保證元素之間的層級關係,通常來講位於同一位置的且層級相同的元素位於同一個Render Layer,只有某些特殊的RenderObject會專門建立一個新的渲染層,其餘的RenderObject與第一個擁有RenderLayer的祖先元素共用一個。常見的生成RenderLayer的RenderObject擁有如下的一種特徵參考[3]chrome

    • 頁面根元素
    • 有CSS定位屬性(relative, absolute, fixed, sticky)
    • transparent不爲1
    • overflow不爲visible
    • 有CSS mask屬性
    • 有CSS box-reflect屬性
    • 有CSS filter屬性
    • 3D或硬件加速的2D canvas元素
    • video元素
  • RenderLayer -> GraphicsLayer:一個或多個RenderLayer對應一個GraphicsLayer(多對一),某些被認爲是Compositing Layer的RenderLayer單獨對應一個GraphicsLayer,其餘RenderLayer與第一個擁有GraphicsLayer的祖先元素共用一個GraphicsLayer。每一個GraphicsLayer有一個GraphicsContext用於繪製其對應的RenderLayers,合成器將GraphicsContexts的位圖合成,最終顯示到屏幕上。渲染層提高爲合成層的緣由以下:canvas

    • 有3D transform屬性
    • 有perspective屬性
    • 3D canvas或硬件加速的2D canvas
    • 硬件加速的iframe元素(如iframe嵌入的頁面有合成層,合成層須要硬件加速)
    • 使用了硬件加速的插件,如flash
    • 對opacity/transform屬性應用了animation/transition(當animation/transition爲active)
    • 子元素是compositing layer
    • 兄弟元素是compositing layer,與當前的非composting layer有重疊,層級低於當前層
    • 有will-change屬性

2、優化方法

在網上能夠看到不少的優化方案總結,大佬們都寫的很好。windows

Talk is cheap. Show me the code.瀏覽器

結合頁面渲染流程,這裏將結合一些測試代碼,分析動畫的各類優化方案和效果:性能優化

  • JavaScript:優化JavaScript的執行效率
    • requestAnimationFrame代替setTimeoutsetInterval
    • 可並行的DOM元素更新劃分爲多個小任務
    • DOM無關的耗時操做放到Web Workers
  • Style:下降樣式計算複雜度和範圍
    • 下降樣式選擇器的複雜度
    • 減小須要執行樣式計算的元素個數
  • Layout:避免大規模、複雜的佈局
    • 避免頻繁改變佈局
    • 用flexbox佈局替代老的佈局模型
    • 避免強制同步佈局事件
  • Paint/Composite:GPU加速
    • 將移動或漸變元素由渲染層(RenderLayer)提高爲合成層(Compositing Layer)
    • 避免提高合成層的陷阱

JavaScript:優化JavaScript的執行效率

1. requestAnimationFrame代替setTimeoutsetInterval

爲何setTimeoutsetInterval很差?
因爲js是單線程執行,因此爲了防止某個任務執行時間過長而致使進程阻塞,js中存在異步隊列的概念,對於如setTimeoutajax請求都是把進程放到了異步隊列中,當主進程爲空時才執行異步隊列中的任務。因此 setTimeoutsetInterval沒法保證回調函數的執行時機,可能會在一幀以內執行屢次致使屢次頁面渲染,浪費CPU資源甚至產生卡頓,或者是在一幀即將結束時執行致使從新渲染,出現掉幀的狀況。
requestAnimationFrame是怎麼優化的?服務器

  • CPU節能,當頁面被隱藏或最小化時,暫停渲染。
  • 函數節流,其循環間隔是由屏幕刷新頻率決定的,保證回調函數在屏幕的每一次刷新間隔中只執行一次。

優化效果具體如何?DEMO
經過chrome的performance面板查看具體表現的差異。
經過setTimeout進行了3次渲染,並且有長時間幀出現:
網絡

setTimeout

使用 requestAnimationFrameDOM操做部分合並,只進行了2次渲染,長時間幀也被優化:
requestAnimationFrame

2. DOM無關的耗時操做放到Web Worker

Web Worker的好處是什麼?
JavaScript是單線程的,若是頻繁的進行耗時操做(如實時更新數據),就會形成擁堵,影響用戶交互體驗。Web Worker的做用在於爲JavaScript建立了多線程環境,worker線程在後臺運行,受主線程控制,二者互不干擾。worker線程負擔高延遲且UI無關的任務,主線程負責UI交互就會相對流暢。
須要注意

  • Web Worker沒法操做DOM,本質上只是將數據刷新和頁面渲染拆開執行。
  • Web Worker遵循同源策略且限制本地訪問。
  • 用一次多餘的網絡請求和瀏覽器線程資源來換取高效執行。

優化效果具體如何?DEMO
能夠經過chrome的performance面板查看具體表現的差異: 不使用web worker,減小了一次網絡請求,可是出現了長時間幀,有卡幀的風險。

不使用worker

使用了 web worker以後,耗時操做無關的任務再也不被阻塞,可是增長了網絡延遲。若是在項目中使用worker,初始化時間須要好好斟酌。
使用worker

可考慮的應用場景

  • 輪詢服務器獲取數據
  • 頻繁的數據上報
  • 耗時的數據處理

Style:下降樣式計算複雜度和範圍

1. 下降樣式選擇器的複雜度?

下降樣式選擇器的複雜度是經常被提出的一個優化方法,實際上這個方法的效果比較微弱,根據Ivan Curic的文章[5]的測試方法(DEMO),在一個擁有50000個節點的頁面中,不一樣選擇器複雜度對於性能的影響不會超過20ms,而通常狀況下,頁面的節點數都不會達到這個數量。
優化效果微弱的緣由在於瀏覽器引擎對選擇器速度進行了優化,不一樣引擎的性能優化方案不一樣,因此開發者的優化是否有效是難以預測的,至少對於靜態元素的優化性價比是極低的。
經過測試能夠確認的一點是,應當減小僞類選擇器和過長的選擇器的使用。推薦按照如OOCSS、BEM等命名規範來組織CSS,優勢是在微弱優化性能的同時也提升了代碼可維護性。

2. 減小須要執行樣式計算的元素個數

這一點是針對較早的瀏覽器而言,較早的瀏覽器如改變了body元素上的一個類,則其子元素都須要從新計算樣式。
現代瀏覽器都進行了優化,因此優化效果要視具體應用場景而言。目前還沒有挖掘到應用例子,後期若有發現回來填坑。

Layout:避免大規模、複雜的佈局

1. 避免頻繁觸發佈局

不一樣的屬性致使的渲染成本不盡相同,這一點在css動畫時對比尤爲明顯。觸發layout或者paint的動畫屬性尤爲消耗性能,因此應當儘可能使用transformopacity做爲動畫屬性,若是沒法實現則考慮採用JavaScript實現動畫。
性能差異有多大? 以width和transform爲例,分別實現動畫的性能差異:DEMO
經過width實現動畫,幀率較低且曲線抖動明顯,右下角也給出了一幀的渲染過程,觸發了樣式計算,佈局,繪製和渲染層合併:

width實現動畫

經過transform實現動畫,能夠發現幀率雖然也低可是平穩,渲染過程只觸發了樣式計算和、繪製和渲染層合併(僅當元素爲合成層時,不會觸發繪製。後面將詳細講述):
transform實現動畫

2. 用flexbox佈局替代老的佈局模型

經常使用的經典佈局方案有基於浮動的佈局、基於絕對定位的佈局,flexbox佈局相較而言更加高效。在能用flexbox佈局的項目中,儘可能用flexbox佈局。如下DEMO嘗試用三種佈局方式渲染同樣的界面效果來測試性能:
絕對佈局:對於每個元素都須要惟一的定位座標,當元素較多時,CSS文件偏大,致使在樣式計算上花費了較多的時間。

絕對佈局

浮動佈局:浮動元素之間定位會互相影響,部分浮動元素也受到文檔流影響,致使佈局所需時間較長。
浮動佈局

彈性佈局:對比前兩種佈局方案而言,性能有較顯著的提高。
彈性佈局

3. 避免強制同步佈局事件

什麼是強制同步佈局?
前面提到了頁面渲染流程是JavaScript->Style->Layout->Paint->Composite,強制同步佈局就是強制瀏覽器在執行JavaScript腳本前先執行佈局。
什麼狀況會致使強制同步佈局?
JavaScript運行時,獲取到的元素屬性樣式都是上一幀的數值,因此若是在當前幀的渲染流程中,獲取當前幀的某個元素屬性以前對該元素進行了修改,瀏覽器就必須先應用屬性再執行JavaScript邏輯,簡而言之就是DOM先寫後讀操做,尤爲是連續的讀寫操做,對瀏覽器的性能影響更大。 對性能影響有多大?DEMO
DEMO經過改變1000個節點的屬性,測試強制同步佈局事件對性能的影響,具體參照下圖。能夠發現性能的損耗是極大的,連續的讀寫操做致使連續的強制同步事件觸發,JavaScript執行時間變得很長:

強制同步佈局

Paint/Composite:GPU加速

1. 將移動或漸變元素由渲染層(RenderLayer)提高爲合成層(Compositing Layer)

注:可在Chrome的開發者工具的layers面板查看合成層,layers面板打開方法command+shift+p(mac)/ctrl+shift+p(windows) -> show layers 將複雜/頻繁變化的元素提高到合成層,這樣的好處是該元素繪製的時候不會觸發其餘元素的繪製。渲染層提高爲合成層的緣由以下(注意如下緣由是在渲染層的基礎之上):

  • 有3D transform屬性
  • 有perspective屬性
  • 3D canvas或硬件加速的2D canvas
  • 硬件加速的iframe元素(如iframe嵌入的頁面有合成層,合成層須要硬件加速)
  • 使用了硬件加速的插件,如flash/iframe
  • 對opacity/transform屬性應用了animation/transition(當animation/transition爲active)
  • will-change屬性爲opacity、transform、top、left、bottom、right
  • 子元素是compositing layer
  • 兄弟元素是compositing layer,與當前的非composting layer有重疊,composting layer的層級低於非composting layer層

爲何會有性能提高?

  • 只重繪須要重繪的部分
  • GPU加速:合成層的位圖直接由GPU合成,比CPU處理速度更快

性能提高有多少? DEMO 經過demo能夠看到,提高爲合成層以後,paint所需的時間大大減小。

render layer -> compositing layer

提高合成層是否是越多越好?
能夠看到提高合成層後,paint時間大大降低。可是合成層的建立須要消耗額外的內存和管理資源,過多的合成層給頁面帶來的內存開銷很大,DEMO建立了5000個元素,所有元素都提高爲合成層與不提高時的內存消耗進行對比。這一點在移動端尤爲須要注意,相比較於PC,移動設備的內存資源更加緊張。

過多合成層

只提高動畫元素的渲染層
基於提高爲合成層來提高性能的原理,當頁面其餘部分繪製比較複雜且相對靜態時,咱們能夠考慮將動畫元素單獨提高爲合成層,減小動畫元素對頁面其餘元素的影響。

2. 避免提高合成層的陷阱

回顧一下提高爲合成層的最後一個緣由:兄弟元素是compositing layer,與當前的非composting layer有重疊,composting layer的層級低於非composting layer層。
這種狀況下致使的提高合成層通常都是預期外的。其緣由與屏幕的渲染流程有關,咱們回憶一下頁面映射的最後一步,每個Compositing Layer對應一張位圖,合成器最後將這些位圖根據層級關係合併起來最終輸出到屏幕。此時咱們假設A是已知的合成層,而B理想中應當是普通渲染層,其層級關係如圖所示:

層級陷阱

B做爲普通渲染層與父級元素位於同一張位圖,A單獨在一張位圖,此時合併的時候層級就會出現問題,若是直接將B置於A之上,有可能致使層級低於A的B的父元素反而顯示在了A之上,反之A,B的層級關係就不對了。瀏覽器此時的解決方案,就是將B也單獨出來做爲compositing layer進行渲染,致使了意料外的compositing layer生成。 這種時候第一直覺就是避免重疊的發生不就行了嘛?然而事情並不簡單。在查找資料的時候發現了一個神奇寶貝—— assumedOverlap。字面意思是假設重疊,對於沒法/難以判斷是否會與compositing layer重合的某些元素,瀏覽器假設會發生重疊,提高爲compositing layer。
對此瀏覽器也進行了優化的,經過層壓縮(Layer Squashing)處理,將與合成層有重疊且連續多個的渲染層合併爲一個合成層。防止因爲重疊致使的提高合成層過多,致使的層爆炸(Layer Explosion),可參考 DEMO
然而層壓縮仍是有解決不了的狀況,查看 源碼能夠列出如下緣由(注意一下都是在重疊/假設重疊的前提下):

  • scrollsWithRespectToSquashingLayer:渲染層相對於壓縮層滾動,當滾動的渲染層與合成層重疊時,會有新的合成層生成且沒法壓縮。DEMO(這個例子不是很好,codepen用iframe嵌入,整個iframe都變成了合成層,若是想看效果能夠在本地看)
  • squashingSparsityExceeded:渲染層壓縮後會致使壓縮層過於稀疏。DEMO
  • squashingClippingContainerMismatch:渲染層和壓縮層的裁剪容器(clip container)不一樣,簡單理解就是重疊的渲染層的容器overflow類型不一樣。DEMO
  • squashingOpacityAncestorMismatch:渲染層與壓縮層的繼承自祖先的opacity屬性不一樣。DEMO
  • squashingTransformAncestorMismatch:渲染層與壓縮層的繼承自祖先的transform不一樣。DEMO
  • squashingFilterAncestorMismatch:渲染層與壓縮層的繼承自祖先的filter屬性不一樣,或者是渲染層自己有filter屬性。DEMO
  • squashingWouldBreakPaintOrder:沒法在不打亂渲染順序的前提下壓縮(e.g. 父元素有mask/filter屬性,子元素與壓縮層overlap,則假如合併了,父元素的mask/filter屬性沒法局部應用在壓縮層,致使渲染結果有誤)。DEMO
  • squashingVideoIsDisallowed:video元素沒法被壓縮。DEMO
  • squashedLayerClipsCompositingDescendants:當合成層是被剪切的子元素時,與之重疊的渲染層沒法被壓縮。DEMO
  • squashingLayoutPartIsDisallowed:沒法壓縮frame/iframe/plugin。
  • squashingReflectionDisallowed:沒法壓縮有reflection屬性的渲染層。 DEMO
  • squashingBlendingDisallowed:沒法壓縮有blend mode屬性的渲染層。DEMO
  • squashingNearestFixedPositionMismatch:渲染層的最近fixed元素與壓縮層不一樣,沒法被壓縮。DEMO

當發現頁面明明沒有什麼內容卻比較卡的時候能夠檢查一下是否是這個緣由,如下給出常見的層壓縮解決不了的狀況:

  1. transform動畫的元素,其後的元素爲relative/absolute定位
    緣由:relative元素和relative下的absolute元素因爲assumedOverlap緣由都被被提高爲合成層,又因爲設置了overflow:hidden,基於前面提到的squashingClippingContainerMismatch,渲染層與合成層的裁剪容器不一樣,致使沒法層壓縮,出現過多的合成層。 解決方法:爲動畫的元素設置z-index擾亂compositing layer的排序。DEMO

3、參考

本文結構主要參照文章[1],對其中的一些優化點進行了實際測試和擴展,也算是一篇讀後感吧~
關於層壓縮部分狀況過於複雜,沒找到什麼資料,感受尚未徹底吃透,後面有機會再從新整理一下。感恩如下大佬!

  1. 深度剖析瀏覽器渲染性能原理,你到底知道多少? www.jianshu.com/p/a32b890c2…
  2. Optimizing CSS: ID Selectors and Other Myths www.sitepoint.com/optimizing-…
  3. GPU Accelerated Compositing in Chrome www.chromium.org/developers/…
  4. GPU加速是什麼 aotu.io/notes/2017/…
  5. Blink Compositing Update: Recap and Squashing docs.google.com/presentatio…
  6. 無線性能優化:Composite taobaofed.org/blog/2016/0…

撒花完結~歡迎指教~

相關文章
相關標籤/搜索