網頁渲染性能優化 —— 性能優化下

網頁渲染性能優化 —— 性能優化下

Composite 的優化

終於,我們到了像素管道的末尾。對於這一部分的優化策略,我們可以從為什麼需要 Composited Layer(Graphics Layer)來入手。這個問題我們在構建 Graphics Layer Tree 的時候,已經說明過,現在簡單回顧一下:

  1. 避免不必要的重繪。
  2. 利用硬體加速高效實現某些 UI 特性。

根據 Composited Layer 的這兩個特點,可以總結出以下幾點優化措施。

使用 transform 和 opacity 屬性來實現動畫

上文我們說過像素管道的 Layout 和 Paint 部分是可以略過,只進行 Composite 的。實現這種渲染方式的方法很簡單,就是使用只會觸發 Composite 的 CSS 屬性;目前,滿足這個條件的 CSS 屬性,只有 transform 和 opacity。

使用 transform 和 opacity 需要注意的是:元素必須是 Composited Layer;如果不是,Paint 還是會照常觸發(Layout 要看情況,一般 transform 會觸發)。來看一個例子:

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width_=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .div { width: 100px; height: 100px; background-color: #f00; /* will-change: transform; */ } </style> <title>性能優化</title></head><body> <div class="div"></div> <script> const div = document.querySelector(".div"); const run = () => { div.style.transform = "translate(0, 100px)"; }; setTimeout(run, 2000); </script></body></html>

我們將使用 transform 來向下位移,開始我們先不把 div 節點提升為 Composited Layer;通過下圖可以看到:還是會觸發 Layout 和 Paint 的。

這時,把 div 節點提升為 Composited Layer,我們發現 Layout 和 Paint 已經被略過了,符合我們的預期。

減少繪製的區域

如果不能避免繪製,我們就應該儘可能減少需要重繪的區域。例如,頁面頂部有一塊固定區域,當頁面某個其他區域需要重繪的時候,很可能整塊屏幕都要重繪,這時,固定區域也會被波及到。像這種情況,我們就可以把需要重繪或者受到影響的區域提升為 Composited Layer,避免不必要的繪製。

提升成 Composited Layer 的最佳方式是使用 CSS 的 will-change 屬性,它的詳細說明可以查看 MDN 的文檔。

.element { will-change: transform;}

對於不支持的瀏覽器,最簡單的 hack 方法,莫過於使用 3D 變形來提升為 Composited Layer 了。

.element { transform: translateZ(0);}

根據上文所講的例子,我們嘗試使用 will-change 屬性來讓固定區域避免重繪。

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width_=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .div { width: 100px; height: 100px; background-color: #f00; } .header { position: fixed; z-index: 9999; width: 100%; height: 50px; background-color: #ff0; /* will-change: transform; */ } </style> <title>性能優化</title></head><body> <header class="header">固定區域</header> <div class="div">變動區域</div> <script> const div = document.querySelector(".div"); const run = () => { div.style.opacity = 0.5; }; setTimeout(run, 2000); </script></body></html>

首先,我們來看下沒有經過優化的情況;順帶說明查看瀏覽器一幀繪製詳情的過程。

  1. 打開控制台的 Performance 界面。
  2. 點擊設置(標記 1),開啟繪製分析儀(標記 2)。
  3. 啟動 Record(標記 3),獲取到想要的信息後,點擊 Stop(標記 4), 停止 Record。
  4. 點擊這一幀的 Paint(標記 5)查看繪製詳情。
  5. 切換到 Paint Profiler 選項卡(標記 6),查看繪製的步驟。

通過上面的圖片(標記 7 和標記 8)可以看到,固定區域的確被波及到,並且觸發重繪了。我們再對比使用 will-change 屬性優化過的情況,發現固定區域沒有觸發重繪。

並且,我們也可以通過一幀(標記 1)的布局詳情(標記 2),查看固定區域(標記 3)是不是提升成 Composited Layer(標記 4),才避免的不必要繪製。

合理管理 Composited Layer

提升成 Composited Layer 的確會優化性能;但是,要知道創建一個新的 Composited Layer 必須要額外的內存和管理,這是非常昂貴的代價。所以,在內存資源有限的設備上,Composited Layer 帶來的性能提升,很可能遠遠抵不上創建多個 Composited Layer 的代價。同時,由於每一個 Composited Layer 的點陣圖都需要上傳到 GPU;所以,不免需要考慮 CPU 和 GPU 之間的帶寬以及用多大內存處理 GPU 紋理的問題。

我們通過 1000 個 div 節點,來對比普通圖層與提升成 Composited Layer 之後的內存使用情況。可以發現差距還是比較明顯的。

最小化提升

通過上文的說明,我們知道 Composited Layer 並不是越多越好。尤其是,千萬不要通過下面的代碼提升頁面的所有元素,這樣的資源消耗將是異常恐怖的。

* { /* or transform: translateZ(0) */ will-change: transform;}

最小化提升,就是要盡量降低頁面 Composited Layer 的數量。為了做到這一點,我們可以不把像 will-change 這樣能夠提升節點為 Composited Layer 的屬性寫在默認狀態中。至於這樣做的原因,我會在下面講解。

看這個例子,我們先把 will-change 屬性寫在默認狀態里;然後,再對比去掉這個屬性後渲染的情況。

.box { width: 100ox; height: 100px; background-color: #f00; will-change: transform; transition: transform 0.3s;}.box:hover { transform: scale(1.5);}

使用 will-change 屬性提升的 Composited Layer:

普通圖層:

我們發現區別僅在於,動畫的開始和結束,會觸發重繪;而動畫運行的時候,刪除或使用 will-change 是沒有任何分別的。

我們在構建 Graphics Layer Tree 的時候講到過這樣一條理由:

對 opacity、transform、fliter、backdropfilter 應用了 animation 或者 transition(需要是 active 的 animation 或者 transition,當 animation 或者 transition 效果未開始或結束後,提升的 Composited Layer 會恢復成普通圖層)。

這條理由賜予了我們動態提升 Composited Layer 的權利;因此我們應該多利用這一點,來減少不必要的 Composited Layer 的數量。

防止層爆炸

我們在 Graphics Layer Tree 中介紹過層爆炸,它指的是由於重疊而導致的大量額外 Composited Layer 的問題。瀏覽器的層壓縮可以在很大程度上解決這個問題,但是,有很多特殊的情況,會導致 Composited Layer 無法被壓縮;這就很可能產生一些不在我們預期中的 Composited Layer,也就是說還是會出現大量額外的 Composited Layer。

在層壓縮這一節,我們已經給出了使用層壓縮優化的例子,這裡就不再重複了。下面再通過解決一個無法被層壓縮的例子,來更為深入的了解如何防止層爆炸。

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width_=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <style> .animating { width: 300px; height: 30px; line-height: 30px; background-color: #ff0; will-change: transform; transition: transform 3s; } .animating:hover { transform: translateX(100px); } ul { padding: 0; border: 1px solid #000; } .box { position: relative; display: block; width: auto; background-color: #00f; color: #fff; margin: 5px; overflow: hidden; } .inner { position: relative; margin: 5px; } </style> <title>性能優化</title></head><body> <div class="animating">動畫</div> <ul> <li class="box"> <p class="inner">提升成合成層</p> </li> <li class="box"> <p class="inner">提升成合成層</p> </li> <li class="box"> <p class="inner">提升成合成層</p> </li> <li class="box"> <p class="inner">提升成合成層</p> </li> <li class="box"> <p class="inner">提升成合成層</p> </li> </ul></body></html>

當我們的滑鼠移入 .animating 元素的時候,通過查看 Layers 面板,可以很清晰的看到出現的大量 Composited Layer。

這個例子雖然表面上看起來沒有發生重疊;但是,因為在運行動畫的時候,很可能與其他元素造成重疊,所以 .animating 元素會假設兄弟元素在一個 Composited Layer 之上。這時,又因為 .box 元素設置了 overflow: hidden; 導致自己與 .animating 元素有了不同的裁剪容器(Clipping Container),所以就出現了層爆炸的現象。

解決這個問題的辦法也很簡單,就是讓 .animating 元素的 z-index 比其他兄弟元素高。因為 Composited Layer 在普通元素之上,所以也就沒有必要提升普通元素,修正渲染順序了。這裡我在順便多說一句,默認情況下 Composited Layer 渲染順序的優先順序是比普通元素高的;但是在普通元素設置 position: relative; 之後,因為層疊上下文,並且在文檔流後面的原因,所以會比 Composited Layer 的優先順序高。

.animating { position: relative; z-index: 1; ...}

當然,如果兄弟元素一定要覆蓋在 Composited Layer 之上,那我們也可以把 overflow: hidden; 或者 position: relative; 去掉,來優化 Composited Layer 創建的數量或者直接就不創建 Composited Layer。

參考資料

  1. 無線性能優化:Composite
  2. 堅持僅合成器的屬性和管理層計數
  3. 簡化繪製的複雜度、減小繪製區域
  4. CSS Animation性能優化
  5. 使用CSS3 will-change提高頁面滾動、動畫等渲染性能
  6. CSS3硬體加速也有坑
  7. 深入理解CSS中的層疊上下文和層疊順序

總結

本文首先講了渲染需要構建的一些樹,然後通過這些樹與像管道各部分的緊密聯繫,整理了一些優化措施。例如,我們對合成所進行的優化措施,就是通過 Graphics Layer Tree 來入手的。

優化也不能盲目去做,例如,提升普通圖層為 Composite Layer 來說,使用不當,反而會造成非常嚴重的內存消耗。應當善加利用 Google 瀏覽器的調試控制台,幫助我們更加詳盡的了解網頁各方面的情況;從而有針對性的優化網頁。

文章參考了很多資料,這些資料都在每一節的末尾給出。它們具有非常大的價值,有一些細節,本文可能並沒有整理,可以通過查看它們來更為深入的了解。


推薦閱讀:

document.write 的痛
MySQL性能優化的最佳21條經驗
用Golang處理每分鐘百萬級請求
快速排序演算法的優化思路總結

TAG:性能優化 | gpu渲染 | 網路動畫webanimation |