從瀏覽器渲染層面解析css3動效優化原理

語言: CN / TW / HK

引言

在h5開發中,我們經常會需要實現一些動效來讓頁面視覺效果更好,談及動效便不可避免地會想到動效效能優化這個話題:

  • 減少頁面DOM操作,可以使用CSS實現的動效不多出一行js程式碼
  • 使用絕對定位脫離讓DOM脫離文件流,減少頁面的重排(relayout)
  • 使用CSS3 3D屬性開啟硬體加速

那麼,CSS3與動效優化有什麼關係呢,本文將從瀏覽器渲染層面講述CSS3的動效優化原理

瀏覽器頁面展示過程

首頁,我們需要了解一下瀏覽器的頁面展示過程:
瀏覽器頁面展示過程

  • Javascript:主要負責業務互動邏輯。
  • Style: 根據 CSS 選擇器,對每個 DOM 元素匹配對應的 CSS 樣式。
  • Layout: 具體計算 DOM 元素顯示在螢幕上的大小及位置。
  • Paint: 實現一個 DOM 元素的可視效果(顏色、邊框、陰影等),一般來說由多個渲染層完成。
  • Composite: 當每個層繪製完成後,瀏覽器會將所有層按照合理順序合併為一個圖層,顯示到螢幕。 本文我們將重點關注 Composite 過程。

瀏覽器渲染原理

在討論 Composite 之前,我們還需要了解一下瀏覽器渲染原理 瀏覽器渲染原理

從該圖中,我們可以發現:

  • DOM 元素Layout Object 存在一一對應的關係
  • 一般來說,擁有相同座標空間的 Layout Object 屬於同一個 Paint Layer (渲染層),通過 position、opacity、filter等 CSS 屬性可以建立新的 Paint Layer
  • 某些特殊的 Paint Layer 會被認為是 Composite Layer (合成層/複合層),Composite Layer 擁有單獨的 Graphics Layer (圖形層),而那些非 Composite Layer 的 Paint Layer,會與擁有 Graphics Layer 的父層共用一個

Graphics Layer

我們日常生活中所看到螢幕可視效果可以理解為:由多個位圖通過 GPU 合成渲染到螢幕上,而點陣圖的最小單位是畫素。如下圖:

那麼點陣圖是怎麼獲得的呢,Graphics Layer 便起到了關鍵作用,每個 Graphics Layer 都有一個 Graphics Context, 點陣圖是儲存在共享記憶體中,Graphics Context 會負責將點陣圖作為紋理上傳到GPU中,再由GPU進行合成渲染。如下圖:

CSS在瀏覽器渲染層面承擔了怎樣的角色

大多數人對於CSS3的第一印象,就是可以通過3D(如transform)屬性來開啟硬體加速,許多同學在重構某一個專案時,考慮到動畫效能問題,都會傾向:

  1. 將2Dtransform改為3Dtransform 2.將 left ( top、bottom、right )的移動改為 3Dtransform
    但開啟硬體加速的底層原理其實就在於將 Paint Layer 提升到了 Composite Layer 以下的幾種方式都用相同的作用:
  • 3D屬性開啟硬體加速(3d-transform)
  • will-change: (opacity、transform、top、left、bottom、right)
  • 使用fixed或sticky定位
  • 對opacity、transform、filter應用了 animation(actived) or transition(actived),注意這裡的 animation 及 transition 需要是處於啟用狀態才行

我們來寫兩段 demo 程式碼,帶大傢俱體分析一下實際情況

demo1. 3D屬性開啟硬體加速(3d-transform)

.composited{
  width: 200px;
  height: 200px;
  background: red;
  transform: translateZ(0)
}
</style>

<div class="composited">
  composited - 3dtransform
</div>

可以看到是因為使用的CSS 3D transform,建立了一個複合層

demo2. 對opacity、transform、filter應用 animation(actived) or transition(actived)

<style>
@keyframes move{
  0%{
    top: 0;
  }
  50%{
    top: 600px;
  }
  100%{
    top: 0;
  }
}
@keyframes opacity{
  0%{
    opacity: 0;
  }
  50%{
    opacity: 1;
  }
  100%{
    opacity: 0;
  }
}

#composited{
  width: 200px;
  height: 200px;
  background: red;
  position: absolute;
  left: 0;
  top: 0;
  
}
.both{
  animation: move 2s infinite, opacity 2s infinite;
}
.move{
  animation: move 2s infinite;
}
</style>

<div  id="composited" class="both">
  composited - animation
</div>
<script>
setTimeout(function(){
  const dom = document.getElementById('composited')
  dom.className = 'move'
},5000)
</script>

這裡我們定義了兩個keyframes(move、opacity),還有兩個class(both、move),起初 #compositedclassName = 'both',5秒延時器後,className = 'move',我們來看看瀏覽器的實際變化。

起初:#composited 建立了一個複合層,並且運動時 fps 沒有波動,效能很穩定 起初

5秒後:複合層消失,運動時 fps 會發生抖動,效能開始變得不再穩定 5秒後

如何檢視複合層及fps

在瀏覽器的 Dev Tools 中選擇 More tools,並勾選 Rendering 中的 FPS meter

動畫效能最優化

之前,我們提到了頁面呈現出來所經歷的渲染流水線,其實從效能方面考慮,最理想的渲染流水線是沒有佈局和繪製環節的,為了實現上述效果,就需要只使用那些僅觸發 Composite 的屬性。
目前,只有兩個屬性是滿足這個條件的:transformsopacity(僅部分瀏覽器支援)。
相關資訊可檢視:css Triggers

總結

提升為合成層簡單說來有以下幾點好處:

  • 合成層的點陣圖,會交由 GPU 合成,比 CPU 處理要快
  • 當需要 repaint 時,只需要 repaint 本身,不會影響到其他的層
  • 對於 transform 和 opacity 效果,部分瀏覽器不會觸發 Layout 和 Paint, 相關資訊可檢視:css Triggers

缺點:

  • 建立一個新的合成層並不是免費的,它得消耗額外的記憶體和管理資源。
  • 紋理上傳後會交由 GPU 處理,因此我們還需要考慮 CPU 和 GPU 之間的頻寬問題、以及有多大記憶體供 GPU 處理這些紋理的問題

大多數人都很喜歡使用3D屬性 translateZ(0) 來進行所謂的硬體加速,以提升效能。但我們還需要切實的去分析頁面的實際效能表現,不斷的改進測試,這樣才是正確的效能優化途徑。

參考資料

無線效能優化:Composite - 淘系前端團隊


歡迎關注凹凸實驗室部落格:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章。