前不久寫了一篇關於如何使用 Chrome DevTools 優化高德地圖動畫的文章,其中提到了 composite,可是並無細談。思考許久,仍是以爲有必要再總結一下。
css
通俗來講:在 DOM 樹中每一個節點都會對應一個 LayoutObject,當他們的 LayoutObject 處於相同的座標空間時,就會造成一個 RenderLayers ,也就是渲染層。
RenderLayers 來保證頁面元素以正確的順序合成,這時候就會出現層合成(composite),從而正確處理透明元素和重疊元素的顯示。node
一旦加載並解析頁面,它就在瀏覽器中做爲許多Web開發人員熟悉的結構來表示:DOM。然而,當呈現一個頁面時,瀏覽器有一系列不直接暴露給開發者的中間表示。這些結構中最重要的是層。git
到這裏,層的概念是有了。composite 翻譯過來就是咱們常說的合成,那麼他是怎麼工做的?github
先貼出一張流程圖,稍後咱們細說:
在 Chrome 中其實有幾種不一樣的層類型:web
提到 RenderLayers 不得不說 RenderObjects :
RenderObjects 保持了樹結構,一個 RenderObjects 知道如何繪製一個 node 的內容, 他經過向一個繪圖上下文(GraphicsContext)發出必要的繪製調用來繪製 nodes。chrome
每一個 GraphicsLayer 都有一個 GraphicsContext,GraphicsContext 會負責輸出該層的位圖,位圖是存儲在共享內存中,做爲紋理上傳到 GPU 中,最後由 GPU 將多個位圖進行合成,而後 draw 到屏幕上,此時,咱們的頁面也就展示到了屏幕上。
編程
GraphicsContext 繪圖上下文的責任就是向屏幕進行像素繪製(這個過程是先把像素級的數據寫入位圖中,而後再顯示到顯示器),在chrome裏,繪圖上下文是包裹了的 Skia(chrome 本身的 2d 圖形繪製庫)canvas
對於隱式合成,CSS GPU Animation 中是這麼描述的:
瀏覽器
This is called implicit compositing: One or more non-composited elements that should appear above a composited one in the stacking order are promoted to composite layers — i.e. painted to separate images that are then sent to the GPU.緩存
咱們先來看下面的圖示:
假設一種場景,咱們須要 A 顯示在 B 之上,而後爲 B 添加移動的動畫,這裏就會出現一個邏輯問題:B 由於有動畫,被提高到了合成層,最終在 GPU 上合成了屏幕圖像,而 A 須要顯示在 B 之上,咱們並無作任何處理。因此爲了使 A 和 B 正常顯示,咱們須要設置 z-index ,這時瀏覽器將強制提高 A 爲複合層,隨後進行 repaint。
這時候,composite 隱式合成就出現了。固然,還有更多的場景,咱們繼續。
咱們知道,在某些特定條件下,瀏覽器會主動將渲染層提至合成層,那麼影響 composite 的因素有哪些?
這裏只舉出部分例子,無線性能優化:Composite這篇文章描述的很詳細,這裏就不贅述了。
1.層壓縮:
相似咱們舉出的層隱式合成的例子,可能簡單的重疊就會產生大量的合成層,這樣會佔用不少無辜的 CPU 和 內存資源,嚴重影響了頁面的性能。這一點瀏覽器也考慮到了,所以就有了層壓縮(Layer Squashing)的處理。
瀏覽器的自動的層壓縮也不是萬能的,有不少特定狀況下,瀏覽器是沒法進行層壓縮的。
此處摘錄自無線性能優化:Composite,demo 請查看原文。
若是多個渲染層同一個合成層重疊時,這些渲染層會被壓縮到一個 GraphicsLayer 中,以防止因爲重疊緣由致使可能出現的「層爆炸」。
2.層爆炸:
經過以前的介紹,咱們知道同合成層重疊也會使元素提高爲合成層,雖然有瀏覽器的層壓縮機制,可是也有不少沒法進行壓縮的狀況。也就是說除了咱們顯式的聲明的合成層,還可能因爲重疊緣由不經意間產生一些不在預期的合成層,極端一點可能會產生大量的額外合成層,出現層爆炸的現象。
解決層爆炸的問題,最佳方案是打破 overlap 的條件,也就是說讓其餘元素不要和合成層元素重疊,譬如巧妙的使用 z-index 屬性。
上面提到了層合成的過程會產生內存消耗,那麼咱們如何來評估層消耗的內存,下面舉例來講明:
<!-- jartto test -->
<div id="a"></div>
<div id="b"></div>複製代碼
#a, #b {
will-change: transform;
}
#a {
width: 100px;
height: 100px;
background: rgb(255, 0, 0);
}
#b {
width: 10px;
height: 10px;
background: rgb(255, 0, 0);
transform: scale(10);
}複製代碼
如上,咱們建立了兩個容器 #a 和 #b,#a 的物理尺寸是 100×100px(100×100×3 = 30000 字節),而 #b 只有10×10px(10×10×3 = 300 字節)但放大了 10 倍。#b 因爲存在 will-change 屬性,transform 動畫將經過 GPU 來渲染圖層。
咱們經過圖像的高度乘以圖像的寬度來得到圖像中像素的數量。而後,咱們將其乘以3,由於每一個像素都用三個字節(RGB)描述。那麼不難理解,若是圖像包含透明區域,咱們要乘以4,由於須要額外的字節來描述透明度:(RGBA):100×100×4 = 40000 字節。
從上面的例子中,咱們得出了一個很是有意義的結論,從而幫助咱們去作一些簡單的優化。例如:若是你想要爲一張大圖添加動畫,你能夠先下載縮小版(原版 10% )而後放大顯示。這對用戶是無感知的,可是咱們卻精簡了頁面加載,從而提高了用戶體驗。
很好,咱們能夠經過上述來評估內存消耗了,這裏引出了兩個術語 Reflow 和 Repaint ,簡單溫習一下:
Reflow要比Repaint更花費時間,也就更影響性能。因此在寫代碼的時候,要儘可能避免過多的Reflow。
reflow 的緣由:
減小 reflow / repaint
既然整個渲染過程如此耗時,那麼大多數人喜歡使用 translateZ(0) 與 will-change 開啓硬件加速的行爲就很容易理解了。
轉了一圈,又回到了本文的重點:合成層。提高合成層的最好方式是使用 CSS 的 will-change 屬性。而 will-change 設置爲 opacity、transform、top、left、bottom、right 能夠將元素提高爲合成層。
先來看看 will-change 的瀏覽器支持狀況,點擊查看
整體支持狀況還不錯,因此咱們能夠像下面這樣使用:
#jartto {
will-change: transform;
}複製代碼
固然,對於個別不支持的瀏覽器,咱們使用 translateZ(0) 來解決,點擊查看
#jartto {
transform: translateZ(0);
}複製代碼
We already know that animation of transform and opacity via CSS transitions or animations automatically creates a compositing layer and works on the GPU.
那麼問題來了,硬件加速依賴 GPU ,而 GPU 爲何會比 CPU 快,咱們接着來看。
文中反覆提到了 CPU 和 GPU ,相信不少童鞋可能會產生這樣的疑惑:爲何要開啓硬件加速,以及 GPU 優點到底在哪裏?
咱們先簡單的瞭解一下 GPU 的工做原理,GPU 處理數據的過程大概是這樣的:
從 pu-accelerated-compositing-in-chrome 這篇文章能夠看出,硬件合成的好處有三種:
組成緩存元素的圖像會更快,而這正是 GPU 的強勢之處:它可以很快地用亞像素精度合成圖像,這給動畫增長了顯著的平滑度。
咱們能夠這麼理解,GPU 是一個單獨的計算機:每個現代設備的一個重要部分其實是一個獨立的單元,有本身的處理器和本身的內存和數據處理模型。與其餘應用程序或遊戲同樣,瀏覽器必須像外部設備那樣與 GPU 對話。
更多 GPU 的介紹,請看這裏傳送門,這裏就不扯遠了。
這裏我再補充一點:
咱們能夠說 CPU 所作的工做都在軟件層面,而 GPU 在硬件層面,咱們能夠用軟件(使用 CPU )作任何事情,可是對於圖像處理,一般用硬件會更快,由於GPU使用圖像對高度並行浮點運算作了優化。
這也是我以前一直困擾的地方,咱們一味的強調硬件加速,而忽略了 CPU 自己的做用。大體過程可能以下:
咱們看到了 GPU 確實很強勢,可是咱們最好不要把全部東西一古腦兒拋給 GPU ,問題在於 GPU 並無無限制處理性能,並且一旦資源用完的話,性能就會開始降低了(即便 CPU 並無徹底佔用)。事實上他們有本身的職責,各司其職,各盡其才,才能發揮出更大的做用。
咱們不打無準備的仗,合理的利用工具,才能大大提升編程效率。這裏自薦一篇文章優化高德地圖動畫,內容包括:
原諒我標題黨了,起初是爲了找尋優化高德地圖動畫的方案,結果寫成了實際的例子,一步步介紹了 Chrome DevTools 。文章已經發表,因此就沒在去更改題目,若是能夠的話,我但願的題目是:動畫優化之如何使用 Chrome DevTools
提高爲合成層簡單說來有如下幾點好處:
若是你已經把一個元素放到一個新的合成層裏,那麼可使用 Timeline 來確認這麼作是否真的改進了渲染性能。別盲目提高合成層,必定要分析其實際性能表現。
實際上,在內存資源有限的設備上,合成層帶來的性能改善,可能遠遠趕不上過多合成層開銷給頁面性能帶來的負面影響。同時,因爲每一個渲染層的紋理都須要上傳到 GPU 處理,所以咱們還須要考慮 CPU 和 GPU 之間的帶寬問題、以及有多大內存供 GPU 處理這些紋理的問題。
因此,你就會明白,這裏咱們使用方式二而不使用方式一的緣由了:
/*jartto:方式一*/
@keyframes move {
from { left: 30px; }
to { left: 100px; }
}複製代碼
/*jartto:方式二*/
@keyframes move {
from { transform: translateX(0); }
to { transform: translateX(70px); }
}複製代碼
優化實際上是一個過程,咱們須要一個點一個點的處理、突破。沒有什麼是一蹴而就的,更沒有所謂的銀彈。就像高中物理書中所說的偏差:「偏差是不可避免的,只能減小」。
優化也同樣,咱們只能盡力去作,而不能強求。不斷嘗試,方是永恆。
參考:
無線性能優化:Composite
CSS GPU Animation
web優化之composite
瀏覽器渲染
gpu-accelerated-compositing-in-chrome
視圖渲染、CPU和GPU卡頓緣由及其優化方案