- 原文地址:The Anatomy of a Frame
- 原文做者:Paul
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:WangLeto
- 校對者:Xuyuey, Fengziyin1234, L9m
開發者經常問我關於像素工做流程的某些部分,何時、爲何、發生了什麼。因此我感受值得提供一些參考,有關於將像素顯示在屏幕上的過程裏發生了什麼。前端
警告:文本是 Blink(譯註:Chrome 使用的排版引擎,是 webkit 的分支)和 Chrome 的視角。主線程的大部分任務以某種方式被全部第三方(vendors)任務「共享」,好比佈局和樣式計算結果,可是總的架構可能不是這樣。android
這是真的,讓咱們先看一張圖:ios
將像素放到屏幕上的完整過程。git
下載圖片github
這張小圖上放了太多內容,因此讓咱們詳細看些定義。將上圖與這些定義結合起來可能會有幫助。web
讓咱們從進程開始看:canvas
如今看一下渲染進程中的線程。後端
在許多方面,你都應該把合成線程看作「老大」。雖然這個線程不運行 JavaScript,不進行佈局、繪製內容或者其餘任務,可是它全權負責啓動主線程工做,並將幀運送到屏幕上。若是合成線程不用等待輸入事件的處理,就能夠在等待主線程完成工做時把幀發送出去。瀏覽器
你也能夠想象 Service Worker 和 Web Worker 存在於渲染進程中,雖然我把他們排除在外了,由於他們把事情弄得很複雜。架構
主線程風貌全覽。下載圖片
讓咱們從垂直同步信號到像素,逐步分析這個過程,而後討論一下在徹底版本中事件是怎麼工做的。記住這一點:瀏覽器並不須要執行全部步驟,具體狀況取決於哪些步驟是必需的。例如,若是沒有新的 HTML 要解析,那麼解析 HTML 的步驟就不會觸發。事實上,一般提高性能的最佳方法,只是簡單地移除流程中部分步驟被觸發的須要!
一樣值得注意的是,上圖中 RecalcStyles 和 Layout 下方指向 requestAnimationFrame
的紅色箭頭。在代碼中剛好觸發這兩個狀況是徹底可能的。這種狀況叫作強制同步佈局(或強制同步樣式,Forced Synchronous Layout 和 Forced Synchronous Styles),一般於性能不利。
開始新的一幀。垂直同步信號觸發,開始渲染新的一幀圖像。
輸入事件的處理。從合成線程將輸入的數據,傳遞到主線程的事件處理函數。全部的事件處理函數(touchmove
,scroll
,click
)都應該最早觸發,每幀觸發一次,但也不必定這樣;調度程序會盡力嘗試,可是是否真的每幀觸發因操做系統而異。從用戶交互事件,到事件被交付主線程,兩者之間也存在延遲。
requestAnimationFrame
。這是更新屏幕顯示內容的理想位置,由於如今有全新的輸入數據,又很是接近即將到來的垂直同步信號。其餘的可視化任務,好比樣式計算,由於是在本次任務以後,因此如今是變動元素的理想位置。若是你改變了 —— 好比說 100 個類的樣式,這不會引發 100 次樣式計算;它們會在稍後被批量處理。惟一須要注意的是,不要查詢進行計算才能獲得的樣式或者佈局屬性(好比 el.style.backgroundImage
或 el.style.offsetWidth
)。若是你這樣作了,會致使從新計算樣式,或者佈局,或者兩者都發生,進一步致使強制同步佈局,乃至佈局顛簸。
解析 HTML(Parse HTML)。處理新添加的 HTML,建立 DOM 元素。在頁面加載過程當中,或者進行 appendChild
操做後,你可能看到更多的此過程發生。
從新計算樣式(Recalc Styles)。爲新添加或變動的內容計算樣式。可能要計算整個 DOM 樹,也可能縮小範圍,取決於具體更改了什麼。例如,更改 body 的類名影響可能很大,可是值得注意的是瀏覽器已經足夠智能了,能夠自動限制從新計算樣式的範圍。
佈局(Layout)。計算每一個可見元素的幾何信息(每一個元素的位置和大小)。通常做用於整個文檔,計算成本一般和 DOM 元素的大小成比例。
更新圖層樹(Update Layer Tree)。這一步建立層疊上下文,爲元素的深度進行排序。
Paint。過程分爲兩步:第一步,對全部新加入的元素,或進行改變顯示狀態的元素,記錄 draw 調用(這裏填充矩形,那裏寫點字);第二步是柵格化(Rasterization,見後文),在這一步實際執行了 draw 的調用,並進行紋理填充。Paint 過程記錄 draw 調用,通常比柵格化要快,可是兩部分一般被統稱爲「painting」。
合成(Composite):圖層和圖塊信息計算完成後,被傳回合成線程進行處理。這將包括 will-change
、重疊元素和硬件加速的 canvas 等。
柵格化規劃(Raster Scheduled)和柵格化(Rasterize):在 Paint 任務中記錄的 draw 調用如今執行。過程是在合成圖塊柵格化線程(Compositor Tile Workers)中進行,線程的數量取決於平臺和設備性能。例如,在 Android 設備上,一般有一個線程,而在桌面設備上有時有 4 個。柵格化根據圖層來完成,每層都被分紅塊。
幀結束:各個層的全部的塊都被柵格化成位圖後,新的塊和輸入數據(可能在事件處理程序中被更改過)被提交給 GPU 線程。
發送幀:最後,但一樣很重要的是,圖塊被 GPU 線程上傳到 GPU。GPU 使用四邊形和矩陣(全部經常使用的 GL 數據類型)將圖塊 draw 在屏幕上。
requestIdleCallback
可能會被觸發。這是作些非必要工做的好機會,好比標記分析數據。若是你不熟悉 requestIdleCallback
,Google Developers 上的入門知識能幫到你。在工做流程中深度的排序有兩種版本。
首先是層疊上下文,好比有 2 個絕對定位的重疊的 div。更新圖層樹(Update Layer Tree) 是流程的一部分,保證 z-index
和相似的屬性受到重視。
而後是合成圖層,在上述流程較後的位置,多用於繪製元素。可使用空 transform 技巧(譯註:指使用 translateZ(0,0)
強制開啓硬件加速),或者 will-change: transform
將一個元素提高爲合成圖層,這樣就能輕鬆地使用 transform 動畫(有利於動畫效果!)。可是若是存在重疊元素,瀏覽器也可能須要建立額外的合成圖層,來保持由 z-index
或者其餘屬性指定的深度順序。有趣!
實質上,上面概述的過程都是在 CPU 中完成的。只有最後一部分,圖塊被上傳和移動的過程,是在 GPU 中完成的。
然而,在 Android 上,像素流在柵格化時有所不一樣:GPU 用得更多一些。在 GPU 着色器上用 GL 命令執行 draw 調用,而不是在合成圖塊柵格化線程中進行柵格化。
這就是所謂的 GPU 柵格化,是一種下降繪製(paint)成本的方法。在 Chrome DevTools 中啓用 FPS Meter(FPS 計數),你能夠查看頁面是否使用了 GPU 柵格化。
FPS 計數面板顯示了正在使用 GPU 柵格化。
若是你但願深刻研究,還有不少的資料,好比如何避免在主線程工做,或者瀏覽器渲染更深刻的運做機理。但願這些資料能幫到你:
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。