你是否曾經好奇過瀏覽器是如何渲染網頁的?本文將通過 30 張圖將帶你瞭解瀏覽器渲染進程的內部工作機制。
渲染進程負責處理標籤頁中的所有內容。
在渲染進程中,主線程處理大部分發送給用户的代碼。如果使用 Web Worker 或 Service Worker,部分 JavaScript 會由工作線程處理。另外,合成器線程和光柵化線程也在渲染進程中運行,確保網頁高效、流暢地渲染。
渲染進程的核心任務是將 HTML、CSS 和 JavaScript 轉換為用户可交互的網頁。
一、解析(Parse)
一旦瀏覽器收到第一個數據分塊,它就可以開始解析收到的信息。“解析”是瀏覽器將通過網絡接收的數據轉換為 DOM 和 CSSOM 的步驟,通過渲染器在屏幕上將它們繪製成頁面。
雖然 DOM 是瀏覽器標記的內部表示,但是它也被暴露出來,可以通過 JavaScript 中的各種 API 進行操作。
構建 DOM 樹
第一步是處理 HTML 標記並構造 DOM 樹。HTML 解析涉及到符號化和樹的構造。HTML 標記包括開始和結束標記,以及屬性名和值。如果文檔格式良好,則解析它會簡單而快速。解析器將標記化的輸入解析到文檔中,構建 DOM 樹。
樹結構類似於現實生活中的“樹”,每個點稱為節點,相連的節點稱為父子節點。DOM 樹構建過程示意圖:
構建 DOM 樹的輸入內容是一個 HTML 文件,然後經過 HTML 解析器解析,最終輸出樹狀結構的 DOM。 通過【開發者工具】 ⇒ 【控制枱】,輸入 document 回車後可查看完整的 DOM 樹結構。
可以瞭解到,DOM 和 HTML 內容幾乎是一樣的,但是和 HTML 不同的是,DOM 是保持在內存中樹狀結構, 可以通過 JavaScript 來查詢和修改內容。而要讓 DOM 節點擁有正確的樣式,需要樣式計算。
子資源加載
當解析器發現非阻塞資源,例如一張圖片,瀏覽器會請求這些資源並且繼續解析。當遇到一個 CSS 文件時,解析也可以繼續進行,但是對於 <script> 標籤(特別是沒有 async 或者 defer 屬性的)會阻塞渲染並停止 HTML 的解析。儘管瀏覽器的預加載掃描器加速了這個過程,但過多的腳本仍然是一個重要的瓶頸。
預加載掃描器
瀏覽器構建 DOM 樹時,這個過程佔用了主線程。同時,預加載掃描器會解析可用的內容並請求高優先級的資源,如 CSS、JavaScript 和 web 字體。多虧了預加載掃描器,我們不必等到解析器找到對外部資源的引用時才去請求。它將在後台檢索資源,而當主 HTML 解析器解析到要請求的資源時,它們可能已經下載中了,或者已經被下載。預加載掃描器提供的優化減少了阻塞。
構建 CSSOM 樹
HTML 加載 CSS 的三種方式:
解析CSS文件
由於瀏覽器也無法解析純文本的 CSS 樣式,所以當渲染引擎接收到CSS文本時, 會將 CSS 文本轉換為瀏覽器可以理解的結構 StyleSheets,也就是 CSSOM ,並提供了查詢和修改功能。
通過控制枱 document.styleSheets 可查看結構:
構建 CSSOM 非常快,並且在當前的開發工具中沒有以獨特的顏色顯示。相反,開發人員工具中的“重新計算樣式”顯示解析 CSS、構建 CSSOM 樹和遞歸計算計算樣式所需的總時間。在 web 性能優化方面,它是可輕易實現的,因為創建 CSSOM 的總時間通常小於一次 DNS 查詢所需的時間。
提示瀏覽器你希望如何加載資源
JavaScript 可能會阻止解析
當HTML解析器遇到**<script>標籤時,會暫停文檔解析,直到JavaScript代碼加載、解析和執行完畢。這是因為JavaScript可以使用document.write()**等方法改變DOM結構,因此必須在繼續解析HTML文檔前執行JavaScript。
如果你想知道在 JavaScript 執行過程中會發生什麼,V8 團隊會就此展開討論和博文。
為了順利加載資源,網絡開發者可以通過多種方式向瀏覽器發送提示。以下是簡要總結上述幾種提示瀏覽器如何加載資源的最常見方法:
async和defer屬性
-
async- 異步加載:瀏覽器在解析HTML文檔的同時並行加載腳本。
- 立即執行:一旦腳本加載完成,便立即執行,不會等待HTML文檔的解析完成。
- 非順序執行:多個帶有**
async**屬性的腳本會根據它們加載完成的順序執行,而不是它們在文檔中的順序。 -
優先外部腳本:更適合獨立的、沒有依賴其他腳本或DOM內容的腳本,例如第三方廣告、數據收集或分析腳本。
<script src="script.js" async></script>
-
defer:- 異步加載:瀏覽器在解析HTML文檔的同時並行加載腳本。
- 延遲執行:腳本文件會在 HTML 文檔完全解析和
DOMContentLoaded事件執行之前執行。 - 順序執行:多個帶有**
defer**屬性的腳本會按它們在文檔中的出現順序執行。 -
DOM 依賴:適合需要在完整的DOM加載後執行的腳本,例如依賴於DOM元素的初始化代碼。
<!---->
<script src="script.js" defer></script>
-
<link rel="preload">提前加載資源,以提高頁面加載性能。常用於關鍵資源,比如腳本、樣式表、字體等。
<link rel="preload" href="style.css" as="style"> <link rel="preload" href="script.js" as="script">- 預加載:瀏覽器在預先加載資源,但不執行。資源會被放在瀏覽器緩存中,等到需要的時候立即可用。
- 不影響執行時機:預加載的資源只是提前下載,並不改變它們原本的執行時機。需要在合適的地方使用相同資源的引用來執行或應用。
-
JavaScript 模塊
-
使用
<script type="module">,使JavaScript模塊化,模塊默認異步加載,不阻塞HTML解析。<script type="module" src="script.js"></script>
-
-
dns-prefetch和preconnect-
dns-prefetch: 提前解析 DNS,提高後續請求速度。<link rel="dns-prefetch" href="//example.com"> -
preconnect: 提前與目標服務器建立連接,減少請求延遲。<link rel="preconnect" href="<https://example.com>">
-
- 懶加載(Lazy Loading)
-
延遲加載圖片和其他不可見的元素,減少初始加載時間。
<img src="image.jpg" loading="lazy" alt="example image">
通過這些常見的方法,開發者可以有效地優化資源加載,提高頁面性能和用户體驗。
二、樣式計算
擁有 DOM 並不足以知道頁面的顯示效果,因為我們可以在 CSS 中設置頁面元素的樣式。主線程解析 CSS 並確定每個 DOM 節點的計算樣式。這是關於根據 CSS 選擇器將哪種樣式應用於每個元素的信息。您可以在開發者工具的 computed 部分查看此信息。
CSS 繼承與層疊
瀏覽器會根據CSS規則計算每個元素的樣式屬性值,包括繼承自父元素的屬性值和層疊樣式表(如用户代理樣式表、作者樣式表和用户樣式表)的屬性值。
CSS 繼承
CSS繼承就是每個DOM節點都包含了父節點的樣式。例如:
body { font-size: 20px }
p {color:blue;}
span {display: none}
div {font-weight: bold;color:red}
div p {color:green;}
可瞭解到,例如 body 節點的font-size的屬性,body 節點下的所以子節點都繼承了。
另可通過“開發者工具”->Element,查看“style”標籤:
通過分析,我們可以選擇對應第一區域對應的元素,可查詢改元素的樣式(對應區域 2 中); 並且可通過區域 3 可查看對應樣式的來源心情,其中,UserAgent樣式,是瀏覽器提供的一組默認樣式,如果不修改任何樣式,默認使用的是UserAgent樣式。
CSS 層疊
層疊是 CSS 的一個基本特徵,它定義瞭如何合併來自多個源的屬性值的算法。 CSS的全稱“層疊樣式表”即強調了這點。
可通過“開發者工具” ⇒ Element 標籤,“Computed”查看最後的計算樣式:
樣式計算階段的目的就是為了計算出 DOM 節點中每個元素的具體樣式,在計算過程中遵守 CSS 的繼承和層疊兩個規則。這個階段最終輸出的內容是每個 DOM 節點的樣式, 並被保存在 Computd Style 的結構內。
標準化樣式表中的屬性值
CSS文本中存在很多屬性值,例如1em、blue、bold等屬性值不容易被渲染引擎識別,所以需要將所有值轉換為渲染引擎容易理解的、標準化的計算值,這個 過程就是屬性值標準化。
標準化屬性值:
CSS 文本中的很多屬性值,如2em、blue、bold不容易渲染引擎理解,因此需要將所有值轉換為渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。
我們看到,經過屬性值標準化後,2em被解析成32px,blue被解析成rgb(0, 0, 255),bold被解析成700。
構建渲染樹(Render Tree)
瀏覽器會根據DOM樹和計算好的樣式屬性值構建渲染樹(也稱為佈局樹或框模型樹)。渲染樹是DOM樹的一個副本,但它只包含可見元素,並且每個元素都有一個對應的盒模型(包括寬度、高度、邊距、邊框等)。
三、佈局(迴流)
現在,渲染程序進程知道文檔的結構以及每個節點的樣式,但這不足以渲染頁面。假設你正嘗試通過電話向朋友描述一幅畫。“有一個大的紅色圓圈和一個小的藍色方塊”是不夠的信息,不足以讓你的朋友知道這幅畫究竟是什麼樣子。
佈局是在渲染樹上運行佈局以計算每個節點的幾何體。
Chrome 在佈局階段需完成兩個任務:佈局 和 重排。
- 佈局 是首次確定渲染樹中所有節點的尺寸和位置,以及確定頁面上每個對象的大小和位置的過程。
- 重排 是後續過程中對頁面的任意部分或整個文檔的大小和位置的重新計算。
佈局 Layout(創建佈局樹)
圖:主線程遍歷經過計算的樣式並生成佈局樹的 DOM 樹
其中 DOM 樹中還包含很多不可見的元素,例如 head 標籤,display: none屬性的元素等。所以在顯示之前,需要額外構建一顆只包含可見元素的佈局樹。
為了構建佈局樹,瀏覽器需要:
- 遍歷 DOM 樹:瀏覽器從 DOM 樹的根節點開始,遍歷整個樹,找到所有可見元素。
- 過濾不可見元素:瀏覽器過濾掉不可見元素,如
display: none的元素、visibility: hidden的元素等。 - 創建佈局樹節點:瀏覽器為每個可見元素創建一個佈局樹節點,這個節點包含了元素的基本信息,如元素的 ID、類名、樣式等。
- 建立父子關係:瀏覽器建立佈局樹節點之間的父子關係,確保每個元素的佈局信息與其父元素和子元素的佈局信息保持一致。
在創建完整的佈局樹後,需要計算佈局樹中節點的準確位置。
佈局計算(重排 Reflow)
佈局計算是確定元素幾何形狀的過程。
HTML採用流式佈局模型,其基本原則是按照元素在文檔流中的順序,自左向右、自上而下排列。此外,還有一些特殊佈局方式,如通過**position** 屬性進行定位佈局和通過 float 實現的浮動佈局。
主線程會遍歷DOM樹和計算出的樣式,創建佈局樹,其中包含元素的座標(x 和 y)及邊界框大小等信息。雖然佈局樹的結構可能與DOM樹類似,但它僅包含與頁面上可見內容相關的元素。如果某個元素應用了**display: none ,它將不會出現在佈局樹中(不過, visibility: hidden的元素仍會存在於佈局樹中)。同樣,偽元素(例如p::before { content: "Hi!" }**)會包含在佈局樹中,即使它們不在DOM中。
圖:因換行變化而移動段落的 Box 佈局
圖:因換行變化而移動段落的 Box 佈局
佈局是一項複雜的任務,即使是最簡單的頁面佈局(如從上到下的塊級流)也需要考慮字體大小和換行位置,因為這些會影響段落的大小和形狀,並進而影響下一個段落的位置。
CSS 可以使元素懸浮、裁剪溢出內容以及更改書寫方向,因此佈局階段非常繁重。
CSS 可以使元素懸浮、裁剪溢出內容以及更改書寫方向,因此佈局階段非常繁重。
階段小結
我們瞭解到了渲染流程的前三個階段為:DOM 生成、樣式計算和佈局:
- 瀏覽器不能直接解析 HTML 數據,所以第一步需要將其轉換為 DOM 樹結構;
- 生成 DOM 樹後,接着根據 CSS 樣式表,計算 DOM 樹所以節點的樣式;
- 最後根據計算 DOM 元素的佈局信息,保存在佈局樹中。
佈局樹和渲染樹的關係
佈局樹(Layout Tree)和渲染樹(Render Tree)是瀏覽器渲染頁面的兩個重要數據結構,它們之間有着密切的關係。
以下是佈局樹和渲染樹的關係圖:
DOM樹
|
|-- 佈局樹(Layout Tree)
| |
| |-- 元素的佈局信息(位置、大小等)
|
|-- 渲染樹(Render Tree)
|
|-- 元素的渲染信息(樣式、顏色、字體等)
|-- 渲染器(Renderer)
四、繪製(重繪)
圖:一個人坐在畫布前,手裏拿着畫筆,不確定應該先畫圓圈,還是先畫方形
擁有 DOM、樣式和佈局仍然不足以渲染頁面。假設你正嘗試複製一幅畫。你已經知道元素的大小、形狀和位置,但仍需判斷它們的繪製順序。
例如,系統可能會為某些元素設置 z-index,在這種情況下,按 HTML 中編寫的元素的順序進行繪製會導致呈現錯誤。
圖:頁面元素按 HTML 標記順序顯示,由於未考慮 Z-index 值,導致呈現的圖片有誤
在此繪製步驟中,主線程會遍歷佈局樹來創建繪製記錄。繪製記錄是繪製過程的備註,例如“先提供背景,然後是文本,最後是矩形”。如果你使用 JavaScript 在 <canvas> 元素上繪製了內容,那麼你可能已經熟悉此過程。
圖:主線程遍歷佈局樹並生成繪製記錄
創建繪製記錄
在這個階段,瀏覽器通過遍歷佈局樹來生成一個具有繪製順序的繪製記錄。這一過程類似於在繪畫過程中記錄每個繪製動作的順序,確保圖像按正確的順序顯示。
繪製記錄的組成:
- 背景: 首先繪製元素的背景。
- 內容: 然後繪製內容,例如文本、圖像等。
- 邊框: 最後繪製邊框或陰影。
這些步驟確保了視覺效果的層次正確。瀏覽器生成的繪製記錄包含繪製每個元素及其內容的詳細指令。
[
{type: "background", rect: {...}, color: "#fff"},
{type: "text", position: {...}, text: "Hello World"},
{type: "border", rect: {...}, style: "solid"}
]
構建分層樹(Layer Tree)
為了確定哪些元素需要位於哪些層,主線程會遍歷佈局樹來創建分層樹。瀏覽器的頁面實際上被分層了很多圖層,這些圖層疊加後合成最終的頁面。
圖:遍歷佈局樹生成層樹的主線程
佈局樹中的每個元素不一定都對應一個分層。層是為了優化重繪性能和處理複雜的層疊上下文而引入的。
圖層樹允許瀏覽器將頁面內容分解為更小、可管理的單位,以提高渲染性能和減少重繪的開銷。
通常情況下,並不是佈局樹的每個節點都包含一個圖層,如果一個節點沒有對應的層,那麼這個節點就從屬於父節點的圖層。
提升為單獨圖層的條件:
- 層疊上下文屬性: 具有層疊上下文屬性的元素(例如
position: fixed,z-index屬性等)會被提升為單獨的圖層。
-
剪裁內容: 需要裁剪內容的元素(例如帶有滾動條的元素
overflow: auto或scroll)也會被提升為單獨的圖層。例如所示,文字所顯示的區域超過了200*200的顯示範圍時就產生了剪裁,渲染引擎會把剪裁文字內容 的一部分用於顯示在div區域。出現這種剪裁情況下,渲染引擎會為文字部分單獨創建一個層,如果出現滾動條,滾動條也會 被提升為單獨的層。
<style> div { width: 200; height: 200; overflow:auto; background: gray; } </style> <body> <div > <p>所以元素有了層疊上下文的屬性或者需要被剪裁,那麼就會被提升成為單獨一層,你可以參看下圖:</p> <p>從上圖我們可以看到,document層上有A和B層,而B層之上又有兩個圖層。這些圖層組織在一起也是一顆樹狀結構。</p> <p>圖層樹是基於佈局樹來創建的,為了找出哪些元素需要在哪些層中,渲染引擎會遍歷佈局樹來創建層樹(Update LayerTree)。</p> </div> </body>被裁剪的內容所在單獨圖層的示意圖
生成繪製列表
在基於佈局樹完成分層樹的構建後,渲染引擎會對圖層樹中的每個圖層進行繪製,渲染引擎會把每個圖層的繪製拆分成很多小的繪製指令,然後再把這些指令按照順序組成一個待繪製列表。
drawBackground({...}, "#fff");
drawText({...}, "Hello World");
drawBorder({...}, "solid");
繪製列表:
繪製列表中的指令其實就是讓其執行一個簡單的繪製操作,而每個原生的背景、邊框等都需要單獨 的指令繪製,通過幾條繪製指令來實現繪製一個元素。
“開發者工具”->“Layers”可查看“document”層等的繪製列表過程:
更新渲染流水線的成本很高
在渲染流水線中,最重要的一點是,在每個步驟中,系統都會使用前一操作的結果創建新數據。例如,如果佈局樹中有一些變化,則需要為文檔中受影響的部分重新生成繪製順序。
如果你要為元素添加動畫效果,瀏覽器必須在每一幀之間執行這些操作。我們的大多數顯示屏每秒都會刷新屏幕 60 次 (60 fps);當你在每一幀屏幕上在屏幕上移動內容時,動畫將對用户來説非常流暢。但是,如果動畫缺少其中幀,則頁面將顯得“卡頓”。
圖:時間軸上的動畫幀
即使你的渲染操作能夠與屏幕刷新保持同步,這些計算也會在主線程上運行,這意味着當應用運行 JavaScript 時,這些計算可能會被阻塞。
圖:時間軸上的動畫幀,但有一幀被 JavaScript 屏蔽
你可以將 JavaScript 操作分成幾小塊,並使用 requestAnimationFrame() 安排在每一幀運行。如需詳細瞭解此主題,請參閲優化 JavaScript 執行。你還可以在 Web 工作器中運行 JavaScript,以避免阻塞主線程。
圖:在具有動畫幀的時間軸上運行的小型 JavaScript 塊
五、合成
如何繪製頁面?
現在,瀏覽器已經瞭解了文檔的結構、每個元素的樣式、頁面的幾何圖形以及繪製順序,接下來就要將這些信息轉換為屏幕上的像素,這個過程稱為光柵化(Rasterization)。
在早期版本的 Chrome 中,處理這個問題的一種簡單方法是直接光柵化視口內的部分。如果用户滾動頁面,則移動光柵框架,並通過光柵化更多內容來填補缺失的部分。然而,現代瀏覽器通過運行一個名為 合成 的更復雜過程來優化這一操作。
什麼是合成?
想象你在製作一本圖畫書,每一頁上都有幾個層次的圖畫,比如:背景、樹木、人物和天空。你先分別繪製每一層,然後把它們疊加在一起,形成一個完整的場景。這就是瀏覽器在合成階段所做的事情。
瀏覽器中的合成可將網頁的各個部分分離成**圖層(Tiling) ,分別將它們光柵化,然後在單獨的線程合成器線程(Compositor Thread)**中合成為網頁。如果發生滾動,由於圖層已光柵化,因此只需合成新幀即可。同樣,可以通過移動層和合成新幀來實現動畫效果。
主線程外的光柵和合成
繪製列表只是用來記錄繪製順序和繪製指令的列表,實際上繪製操作是由渲染進程中的合成線程和光柵化線程來完成的。主線程會先將這些信息提交到合成器線程。
當圖層的繪製列表準備完成後,主線程會把該繪製列表提交給合成線程。
圖塊處理(Tiling) :
通常,網頁頁面非常大,而用户只能夠看到其中的一部分,稱為**視口(ViewPort)**。
圖層的大小可能覆蓋整個頁面,繪製所有圖層內容的開銷很大。因此,合成線程會將圖層分割成多個小圖塊(Tiling),並將每個圖塊發送到光柵化線程處理。這些圖塊的大小通常為256x256或512x512像素。
圖:創建圖塊位圖併發送到 GPU 的光柵線程
執行光柵化
合成線程會按照視口附近的圖塊來優先生成位圖(Bitmap),實際生成位圖的操作是由光柵化線程來執行的。 所謂光柵化,是指將圖塊轉換為位圖。而圖塊是光柵化執行的最小單位。渲染進程維護了一個光柵化線程池,所以的圖塊光柵化都是在線程池內執行的。
合成線程提交圖塊給光柵化線程池
利用GPU加速
通常,光柵化過程都會使用 GPU 來加速生成,使用 GPU 生成位圖的過程叫**快速光柵化**或者 GPU光柵化,渲染進程會把生成圖塊的指令發送給 GPU 進程,然後在 GPU進程 將圖塊的矢量數據快速轉換為位圖,並將生成的位圖存儲在GPU內存中。
你可以在開發者工具中使用“Layers”面板查看網站是如何劃分為多個圖層的。
生成合成幀(Composite Frame)
圖塊光柵化完成後,光柵化線程池將已經處理好的位圖圖塊返回給合成線程。
合成線程根據頁面的層次結構和圖塊信息,創建繪製四邊形(Draw Quads),這些四邊形包含圖塊在內存中的位置及它們在頁面上的渲染位置。這一步就像把一個大拼圖分塊拼好,再組合成一張完整的圖片。
合成線程確保每個圖塊都放在正確的位置,將所有繪製四邊形(Draw Quads)組合成一個完整的合成幀。這一合成幀代表了當前需要呈現給用户的完整頁面。
然後,合成線程會通過進程間通信(IPC) ,將合成幀發送到瀏覽器進程中。
圖:創建合成幀的合成器線程。先將幀發送到瀏覽器進程,然後再發送到 GPU
顯示(Display)
瀏覽器進程接收到合成幀後,瀏覽器進程中的UI線程可能會結合瀏覽器自身的界面元素(比如滾動條、瀏覽器擴展等)與接收到的合成幀進行進一步合成。
最終的合成幀通過 IPC 發送到GPU進程,GPU進程 負責將這個合成幀在顯示設備上顯示出來。
GPU進程遵循顯示器的垂直同步(通常每秒60次,即60Hz)將合成幀逐幀呈現到屏幕上,確保頁面渲染的流暢和穩定。
滾動和動畫的優化
若用户滾動頁面,合成線程只需要更新新的滾動部分,而不必重新計算整個頁面內容,這就像更換拼圖的一部分來適應視圖改變。
類似地,對於動畫效果,合成線程只需要不斷更新動畫部分,使得動畫流暢進行,而不會影響其他內容。
小結
瀏覽器渲染過程中的合成階段從進程和線程視角總結如下:
- 光柵化線程池:並行處理大量圖塊,利用GPU加速生成位圖。
- 合成線程:收集和組合圖塊,生成合成幀。
- 進程間通信:合成幀通過IPC發送到瀏覽器進程。
- 瀏覽器進程:結合瀏覽器界面合成完整頁面。
- GPU進程:最終將合成幀顯示在屏幕上,確保流暢的用户體驗。
這整個過程通過優化線程和進程的協同工作,極大地提升了頁面渲染的性能,使得滾動和動畫效果更加流暢。
合成器線程不需要等待樣式計算或 JavaScript 執行。因此,僅合成動畫被認為是實現流暢性能的最佳選擇。如果需要再次計算佈局或繪製,則必須涉及主線程。
六、總結
完整的渲染流水線:
瀏覽器渲染流程的真實順序
-
解析HTML和構建DOM樹:
- 瀏覽器從網絡上獲取HTML文件,開始解析這個文件的內容,逐步構建DOM樹(Document Object Model Tree)。
-
解析CSS和構建CSSOM樹:
- 同時,瀏覽器也開始解析與HTML相關聯的CSS文件,構建一個CSSOM(CSS Object Model Tree)。
-
合成DOM和CSSOM為渲染樹:
- 瀏覽器將DOM樹與CSSOM樹合成,生成渲染樹(Render Tree),使每個DOM節點都有相應的樣式信息。
-
佈局(Layout/Reflow) :
- 渲染樹生成後,瀏覽器計算每個節點的幾何位置和尺寸,這一過程叫做佈局或者回流(Reflow)。
-
繪製(Painting) :
- 繪製記錄(Paint Record) :瀏覽器通過遍歷佈局樹,生成繪製記錄。這些記錄包括了繪製元素的順序和詳細指令。
- 繪製順序處理:確定繪製順序,例如處理**
z-index**等樣式屬性,確保視覺上的正確順序。
-
構建分層樹(Layer Tree) :
-
確定層(Layers) :瀏覽器確定哪些元素需要創建獨立的圖層。例如:
- 擁有層疊上下文屬性的元素(例如**
position: fixed**, **z-index**等)。 - 需要剪裁的元素(例如帶有滾動條的元素**
overflow: auto或scroll**)。
- 擁有層疊上下文屬性的元素(例如**
-
-
生成繪製列表(Paint List) :
- 合成後的每個圖層會有自己的獨立繪製列表。
- 繪製指令:包含繪製指令(例如繪製背景、內容、邊框等),按照正確的順序排列。
-
繪製到位圖(Rasterization) :
- 生成圖塊(Tile) :為了提高性能,大的圖層可能會被拆分成更小的圖塊。
- 光柵化(Rasterization) :將繪製指令轉換為位圖,準備交給GPU處理。這些位圖被稱為紋理(Textures)。
-
合成和渲染(Compositing and Rendering) :
- 合成器線程(Compositor Thread) :獨立於主線程的合成器線程負責將不同圖層的紋理合成到一起。
- GPU加速:使用GPU加速將這些紋理合成並渲染到最終的幀。
- 顯示內容(Display Content) :合成後的圖像最終呈現到用户的屏幕上。
參考文獻
深入瞭解現代網絡瀏覽器(第 3 部分) | Blog | Chrome for Developers
(宏觀視角下的瀏覽器)渲染流程:HTML、CSS和JavaScript是如何變成頁面的? | Web全棧技術筆記
頁面渲染:瀏覽器工作原理(打開頁面全流程) | 風動之石的博客
渲染頁面:瀏覽器的工作原理 - Web 性能 | MDN