首圖是一張經典的Junior前端面試題目: 當你在網址列輸入URL後,按下Enter,到瀏覽器顯示畫面,這中間發生了哪些事?才能完成這一系列的行為?本文只打算聚焦解釋圖片下半部,也就是瀏覽器收到伺服器Responds的HTML檔案,經過一系列解析,終於完成畫面繪製的過程。
Critical Rendering Path
渲染畫面主要是走這個流程,該流程有個名字叫做Critical Rendering Path,主要由瀏覽器的Main Thread執行,在Paint operation步驟中可能交付給其他Thread來執行,接下來就依序介紹Critical Rendering Path各步驟。
DOM Construction
假設瀏覽器收到如下的一個html檔案,他會做什麼事呢?
他會逐行讀取code,遇到一個element tag便創建一個叫做Node的javascript object,所有的element tag都會被轉成Node。最後瀏覽器會將這些節點依照其相依性創造出一個樹狀的結構,便是下圖的DOM tree。
值得一提的是DOM並非來自Javascript,他是瀏覽器提供的web API,瀏覽器將此web API開放給開發人員取用,好讓前端開發人員可以自由的創建想要的畫面。
CSSOM Construction
瀏覽器解析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的資訊,瀏覽器才會有稍後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執行。