瀏覽器是如何完成畫面渲染的?

V-Wang
8 min readJul 12, 2023

--

https://dev.to/wassimchegham/ever-wondered-what-happens-when-you-type-in-a-url-in-an-address-bar-in-a-browser-3dob

首圖是一張經典的Junior前端面試題目: 當你在網址列輸入URL後,按下Enter,到瀏覽器顯示畫面,這中間發生了哪些事?才能完成這一系列的行為?本文只打算聚焦解釋圖片下半部,也就是瀏覽器收到伺服器Responds的HTML檔案,經過一系列解析,終於完成畫面繪製的過程。

Critical Rendering Path

這張圖是看著這篇文章再製的

渲染畫面主要是走這個流程,該流程有個名字叫做Critical Rendering Path,主要由瀏覽器的Main Thread執行,在Paint operation步驟中可能交付給其他Thread來執行,接下來就依序介紹Critical Rendering Path各步驟。

DOM Construction

假設瀏覽器收到如下的一個html檔案,他會做什麼事呢?

一個普通的html檔

他會逐行讀取code,遇到一個element tag便創建一個叫做Node的javascript object,所有的element tag都會被轉成Node。最後瀏覽器會將這些節點依照其相依性創造出一個樹狀的結構,便是下圖的DOM tree。

DOM Tree

值得一提的是DOM並非來自Javascript,他是瀏覽器提供的web API,瀏覽器將此web API開放給開發人員取用,好讓前端開發人員可以自由的創建想要的畫面。

CSSOM Construction

CSSOM Tree的節點帶有style資訊

瀏覽器解析CSS檔案,創建CSSOM Tree,CSSOM Tree的節點上,帶有相對應DOM Tree上的DOM節點的style資訊。不會出現在畫面上的element tag就不會出現在CSSOM Tree上,例如上面DOM Tree圖中的<head>、< meta>、 <link>就沒有在CSSOM Tree中。

CSSOM的節點並不是依序創建,由於CSS可能是寫在同一個html檔案的<style>element tag中、寫在外部檔案再用<link> element tag引入,或是寫成inline-style; 又由於在程式中,CSS樣式的設定是後面的設定會覆蓋掉比較早的樣式設定,所以為了避免讓使用者在瀏覽器渲染頁面時,數度看到畫面上同一個element的style一直變動,導致使用者體驗很差,CSSOM Tree的更新會在stylesheet中所有的樣式都被讀取並處理完後,才一次更新CSSOM Tree,然後才更新下面要提到的Render tree。

Render Tree

Render Tree由DOM Tree和CSSOM Tree合成的

Render Tree就是把DOM Tree跟CSSOM Tree結合起來,有了Render Tree的資訊,瀏覽器才會有稍後Layout跟Paint的動作。

Render Tree帶有最終會呈現在畫面上的節點,所以有些元素如果他的樣式設定是display:none的話,就不會出現在Render Tree上,例如本節Render Tree就沒有被設定了display:none的img的節點。但如果元素的樣式是visibility: hidden,由於visibility:hidden在畫面上雖然是隱形的,但還是會佔有一定的空間,所以被下了visibility: hidden的樣式的元素的節點,就會出現在Render Tree上。

Layout

又稱Reflow,瀏覽器將Render Tree當成input,計算Render Tree上各節點的大小、顏色、位置、形狀等外觀上的物理資訊。Layout的計算除了一開始載入畫面會觸發,也會在使用者滾動或重新調整視窗大小、操作DOM節點的時候觸發。

Paint

Paint裡面又有兩個步驟:Layer和Rasterized。

會需要Layer是因為畫面上的元素可能會互相交疊,並且某些元素會有共同的CSS設置,或是變化時機相同的CSS樣式設置,因此瀏覽器會將元素分組,這種分組便稱為Layer,例如Header是一個Layer、側邊滑出的選單會是另一個Layer。創建Layer使瀏覽器繪製畫面的效能提升,並且開發人員也可以設定畫面上Layer繪製的先後順序(例如z-index的設定)。

有了Layer之後,就會將各Layer依序Rasterized,也就是一次處理一個Layer,將Layer上各元素轉成px,轉成px時把CSS樣式資訊填入各px中。為了提高畫面渲染的效能,瀏覽器可能使用不同的Raster Thread來處理Layer的Rasterized。

原先從DOM創建到Layout都由瀏覽器的Main Thread(單一線程)來處理,但到了Paint這一步,瀏覽器將控制權從Main Thread轉給Compositor Thread,一個Layer可能很大,例如高度有整個頁面這麼長,那麼Compositor Thread會再將Layer分割(Divide),交由不同Raster Thread來Rasterized。

Compositing

上一步的Layer都被Rasterized之後,Compositor Thread將Rasterized後的Layer集結(因為上一步的最後我提過的被分割)成一個Compositor Frame,提交給GPU去繪製畫面。

同場加映:我的動畫為什麼看起來卡卡的?

動畫卡卡的原因很多,此處要提出的是跟本文原理有關的解釋。

前端開發人員寫出來的動畫看起來卡頓或導致網頁渲染速度慢,是因為你使用了會觸發Layout跟Paint重新運算的CSS指令。寫動畫的時候,推薦使用以下兩個指令:transform、opacity。這兩個指令是CSS-based animation,瀏覽器中負責執行的Thread是Compositor Thread,而不是Main Thread。若是用其他方式來寫動畫,例如用top、left使div移動,則會觸發Main Thread執行。Main Thread除了要執行畫面渲染大部分的任務,還肩負執行JavaScript的責任,因此當瀏覽器正在執行某些規模龐大的任務時,你的動畫就沒辦法被執行,因此看起來卡頓。只用到Compositor Thread的動畫不會觸發style recalculation跟JavaScript執行。

Reference

--

--