瀏覽器是如何渲染頁面的?
當瀏覽器的網路執行緒收到 HTML 文件後,會產生一個渲染任務,並將其傳遞給渲染主執行緒的訊息佇列。
在事件迴圈機制的作用下,渲染主執行緒取出訊息佇列中的渲染任務,開啟渲染流程。
整個渲染流程分為多個階段,分別是: HTML 解析、樣式計算、佈局、分層、繪製、分塊、光柵化、畫
每個階段都有明確的輸入輸出,上一個階段的輸出會成為下一個階段的輸入。
這樣,整個渲染流程就形成了一套組織嚴密的生產流水線。
渲染的第一步是解析 HTML。
解析過程中遇到 CSS 解析 CSS,遇到 JS 執行 JS。為了提高解析效率,瀏覽器在開始解析前,會啟動一個預解析的執行緒,率先下載 HTML 中的外部 CSS 檔案和 外部的 JS 檔案。
如果主執行緒解析到link
位置,此時外部的 CSS 檔案還沒有下載解析好,主執行緒不會等待,繼續解析後續的 HTML。這是因為下載和解析 CSS 的工作是在預解析執行緒中進行的。這就是 CSS 不會阻塞 HTML 解析的根本原因。
如果主執行緒解析到script
位置,會停止解析 HTML,轉而等待 JS 檔案下載好,並將全域性程式碼解析執行完成後,才能繼續解析 HTML。這是因為 JS 程式碼的執行過程可能會修改當前的 DOM 樹,所以 DOM 樹的生成必須暫停。這就是 JS 會阻塞 HTML 解析的根本原因。
第一步完成後,會得到 DOM 樹和 CSSOM 樹,瀏覽器的預設樣式、內部樣式、外部樣式、行內樣式均會包含在 CSSOM 樹中。
渲染的下一步是樣式計算。
主執行緒會遍歷得到的 DOM 樹,依次為樹中的每個節點計算出它最終的樣式,稱之為 Computed Style。
在這一過程中,很多預設值會變成絕對值,比如red
會變成rgb(255,0,0)
;相對單位會變成絕對單位,比如em
會變成px
這一步完成後,會得到一棵帶有樣式的 DOM 樹。
接下來是佈局,佈局完成後會得到佈局樹。
佈局階段會依次遍歷 DOM 樹的每一個節點,計算每個節點的幾何資訊。例如節點的寬高、相對包含塊的位置。
大部分時候,DOM 樹和佈局樹並非一一對應。
比如display:none
的節點沒有幾何資訊,因此不會生成到佈局樹;又比如使用了偽元素選擇器,雖然 DOM 樹中不存在這些偽元素節點,但它們擁有幾何資訊,所以會生成到佈局樹中。還有匿名行盒、匿名塊盒等等都會導致 DOM 樹和佈局樹無法一一對應。
下一步是分層
主執行緒會使用一套複雜的策略對整個佈局樹中進行分層。
分層的好處在於,將來某一個層改變後,僅會對該層進行後續處理,從而提升效率。
捲軸、堆疊上下文、transform、opacity 等樣式都會或多或少的影響分層結果,也可以透過will-change
屬性更大程度的影響分層結果。
再下一步是繪製
主執行緒會為每個層單獨產生繪製指令集,用於描述這一層的內容該如何畫出來。
完成繪製後,主執行緒將每個圖層的繪製資訊提交給合成執行緒,剩餘工作將由合成執行緒完成。
合成執行緒首先對每個圖層進行分塊,將其劃分為更多的小區域。
它會從執行緒池中拿取多個執行緒來完成分塊工作。
分塊完成後,進入光柵化階段。
合成執行緒會將塊資訊交給 GPU 程序,以極高的速度完成光柵化。
GPU 程序會開啟多個執行緒來完成光柵化,並且優先處理靠近視口區域的塊。
光柵化的結果,就是一塊一塊的點陣圖
最後一個階段就是畫了
合成執行緒拿到每個層、每個塊的點陣圖後,生成一個個「指引(quad)」資訊。
指引會標識出每個點陣圖應該畫到螢幕的哪個位置,以及會考慮到旋轉、縮放等變形。
變形發生在合成執行緒,與渲染主執行緒無關,這就是transform
效率高的本質原因。
合成執行緒會把 quad 提交給 GPU 程序,由 GPU 程序產生系統呼叫,提交給 GPU 硬體,完成最終的螢幕成像。
什麼是 reflow?
reflow 的本質就是重新計算 layout 樹。
當進行了會影響佈局樹的操作後,需要重新計算佈局樹,會引發 layout。
為了避免連續的多次操作導致佈局樹反覆計算,瀏覽器會合並這些操作,當 JS 程式碼全部完成後再進行統一計算。所以,改動屬性造成的 reflow 是非同步完成的。
也同樣因為如此,當 JS 獲取佈局屬性時,就可能造成無法獲取到最新的佈局資訊。
瀏覽器在反覆權衡下,最終決定獲取屬性立即 reflow。
什麼是 repaint?
repaint 的本質就是重新根據分層資訊計算了繪製指令。
當改動了可見樣式後,就需要重新計算,會引發 repaint。
由於元素的佈局資訊也屬於可見樣式,所以 reflow 一定會引起 repaint。
為什麼 transform 的效率高?
因為 transform 既不會影響佈局也不會影響繪製指令,它影響的只是渲染流程的最後一個「draw」階段
由於 draw 階段在合成執行緒中,所以 transform 的變化幾乎不會影響渲染主執行緒。反之,渲染主執行緒無論如何忙碌,也不會影響 transform 的變化。