原文連接git
翻譯自:The Anatomy of a Frame github
常常被開發者問及像素渲染管道流的細節問題,它每一步是怎樣觸發的,什麼時候觸發的、爲何會觸發。 這篇文章就是爲了解答這些疑惑的,你將瞭解到像素是如何渲染到屏幕的。web
注意:如下講解基於 Blink
內核的 Chrome
瀏覽器。例如像layout
或 style calcs
等步驟運做在瀏覽器主線程中,這在大部分瀏覽器廠商中達成共識,但如下總體架構未必被其餘廠商採納。canvas
在這張圖裏涉及到太多的內容,能夠大體分爲兩個大的部分。 (如下文字須要反覆對照這張圖,爲了方便,做者很貼心的給了圖片地址The Anatomy of a Frame。)api
該流程包含許多線程負責處理不一樣的事物,確保讓你的頁面渲染到屏幕。 主要的線程有以下瀏覽器
該流程服務於上述的線程和瀏覽器其餘的進程。當渲染流程處理完幀後,將觸發 commit
事件。 GPU流程將瓷磚tiles
和其餘數據(例如:quads and matrices)上傳給 GPU
,GPU
將像素輸出到屏幕。 實際GPU流程包含一條單線程GPU Thread
負責與 GPU
通訊。性能優化
如今分析下渲染流程中的線程。多線程
當用戶交互例如輸入框輸入數據,OS觸發 vsync event
首先通知 Compositor Thread
。 若是能夠的話,該線程避免進入主線程,經過 GPU Thread
直接與 GPU
通訊,把用戶的交互行爲渲染到屏幕上。 若是不能夠(事件綁定了回調處理或其餘可視化DOM操做),這時才須要主線程來處理。架構
該線程執行着咱們耳熟能詳的工做,如JavaScript
, styles
, layout
和 paint
等。 因爲大量的工做運行在該線程中,該線程是形成頁面卡頓jank
的"真兇"。app
Compositor Thread
會生成(spawn)一條或多條子線程 Compositor Tile Worker
來處理光柵化任務。
PS:在某種程度上 Compositor Thread
纔是 big boss
,雖然它不處理 JavaScript
、Layout
, Paint
等任務。 但它全面的控制着主線程的初始化和把每幀運輸到屏幕上的工做。
其實 Service Workers
,Web Workers
也存在該過程當中,只是有點複雜,暫且不表。
接下來分析下圖中完整版主線程中的每一個事件是幹什麼工做的。 有一點須要記住的是:在實際中並非每一步都會執行。 例如沒有新的 HTML
須要解析,Parse HTML
就不會執行。 前面提到的卡頓的優化,就是避免渲染管道中的事件反覆執行或避免某部分的執行。 具體參考渲染性能優化的最佳實踐課程。
一樣值得注意的是:styles
和 layout
底下的紅色箭頭指向 requestAnimationFrame
。 requestAnimationFrame
能確保在一幀(實際上是指渲染管道流)開始前執行完畢。 但若是你在 requestAnimationFrame
代碼不注意的話,可能會觸發styles,layout
的提早執行, 致使強制同步重排/佈局 Forced Synchronous Layout
,極大的破壞頁面性能。
PS:之前的一些庫動畫實現例如 jQuery
依賴 setTimeout
或 setInterval
,能夠的話儘可能用 CSS3
和 requestAnimationFrame
替代。
Frame Start
:Vsync
觸發, 一幀開始。Input event handlers
:合成線程 compositor thread
把 input
數據傳給主線程, 處理事件回調。OS調度程序將盡最大努力合理調度事件回調(touchmove, scroll, click等),以及時響應用戶交互。 即使如此,在用戶交互和主線程處理事件得到響應之間多少會有些延遲。requestAnimationFrame
由於它離vsync
很近,能夠就近獲取input data
。因此這是操做dom
理想的地方,例如修改 100
個元素的 class
, 並不會致使100
次樣式計算style calculations
,而是會在以後的管道流中批量處理。 須要注意的是:你不能在查詢任何已計算的樣式或佈局屬性(例如el.style.backgroundImage
, el.style.offsetWidth
)。 若是你這麼作了,就像圖中紅色箭頭標明的同樣,recalc styles
或 layout
或二者會提早執行,將致使強制佈局,更糟會引發頁面抖動。 Avoid Large, Complex Layouts and Layout Thrashing。Parse HTML
:任何新增的 HTML
都會被處理,構建新的DOM
元素。 你能夠在頁面加載或 appendChild
等操做中看到這一過程。Recalc Styles
:對於解析樣式文件或class
或 style
等樣式操做都會引起樣式計算。可能會從新渲染整棵樣式樹。 具體取決於哪一個元素的樣式改變,例如 body
就影響比較大。值得注意的是,瀏覽器已經很聰明能自動限制波及的範圍。Layout
:計算可見元素幾何(盒模型)信息(位置、尺寸)。一般會對整個文檔操做一遍。 產生的開銷和 DOM
個數成正比例關係。Update Layer Tree
:建立層疊上下文和元素層級順序。Paint
:其實這是繪畫的第一步,這一步記錄須要調用繪製的方法 draw calls
(fill a rectangle here, write text there)。 第二步是光柵化 Rasterization
(下面會提到),draw calls
會被執行。 第一步顯然速度要快於第二步,但常常把這兩步都成爲 painting
。Composite
:層 layer
和瓷磚 tile
信息被計算後回傳給 compositor thread
處理。 Composite
負責處理有 will-change
,overlapping elements
,或任何硬件加速的 canvas
。Raster Scheduled and Rasterize
:光柵調度和光柵化,這裏將執行在 Paint
任務中提到的draw calls
。 將在 Compositor Tile Workers
中處理,該線程的多少取決於系統和硬件設備的能力。 例如 Android
一般起一個Compositor Tile Workers
線程,PC 可能有4個。 根據層 layers
信息來光柵化,layers
是由不少瓷磚 tiles
組成。Frame End
:全部 layers
中被光柵化的 tiles
和 input data
(可能被事件回調處理了)將被提交給 GPU Thread
。Frame Ships
:最後,全部的瓷磚 tiles
都將被 GPU Thread
上傳給硬件 GPU
處理。 GPU
將使用quads and matrices
來把 tiles
打印在屏幕上。requestIdleCallback
在主線程處理完一幀後還剩餘一些處理空間的話 requestIdleCallback
會被觸發。 這是一個很是好的機會來處理非必要的工做,如用戶行爲信息等採集。 若是你是一個新手,這裏有份參考:Using requestIdleCallback。
值得一提,最新的 React
利用此api來優化性能。
上面流程說起 layer
有兩中概念,以示區分。
層疊上下文the Stacking Contexts
。例如兩個絕對定位的div
,根據元素先後出現和z-index
會產生層疊關係。 這一過程在 Update Layer Tree
中處理,確保正確的層疊順序。
這在以後的流程中處理,更適用於已繪製的元素的概念。 提高元素 Compositor Layer
層級的方法
will-change: transform
/* transform 不支持的 hack */transform: translateZ(0) /* 3D */
對於層級少 animation
元素能減小性能開銷(避免主線程管道流中的某部分執行),俗稱 GPU加速
。 但瀏覽器可能不得不建立額外的 Compositor Layers
來保存層疊順序(z-index指定), 這就是產生了 overlapping elements
元素。上述主線程描述的過程差很少都發生在CPU
中。只有最後一步,傳輸完後 tiles
在 GPU
中處理。
然而在 Android
中有點不一樣,Compositor Tile Workers
光柵化 Rasterization
的操做,也在 GPU
中完成。 draw calls
做爲 GL 命令在 GPU
着色器中執行。
這被稱爲 GPU Rasterization
,這是一種減小 paint
成本的一種方式。 若是頁面使用了GPU光柵化,能在Chrome DevTools
中的 FPS Meter
查看。
若是你想學習如何避免卡頓 jank
,想對性能優化方面有更高級的認知, 下面這些都能幫助你。