網頁是如何渲染出來的,dom樹和css樹是如何合併的,瀏覽器的運行機制是什麼,什麼是否會造成渲染阻塞?

渲染樹構建、佈局及繪製

瀏覽器的內核是指支持瀏覽器運行的最核心的程序,分爲兩個部分的,一是渲染引擎,另一個是JS引擎。渲染引擎在不同的瀏覽器中也不是都相同的。目前市面上常見的瀏覽器內核可以分爲這四種:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。這裏面大家最耳熟能詳的可能就是 Webkit 內核了,Webkit 內核是當下瀏覽器世界真正的霸主。 本文我們就以 Webkit 爲例,對現代瀏覽器的渲染過程進行一個深度的剖析。
在介紹瀏覽器渲染過程之前,我們簡明扼要介紹下頁面的加載過程,有助於更好理解後續渲染過程。
要點如下:

1)瀏覽器會解析三個東西:

一個是HTML/SVG/XHTML,事實上,Webkit有三個C++的類對應這三類文檔。解析這三種文件會產生一個DOM Tree。
CSS,解析CSS會產生CSS規則樹。
Javascript,腳本,主要是通過DOM API和CSSOM API來操作DOM Tree和CSS Rule Tree.
2)解析完成後,瀏覽器引擎會通過DOM Tree 和 CSS Rule Tree 來構造 Rendering Tree。注意:

Rendering Tree 渲染樹並不等同於DOM樹,因爲一些像Header或display:none的東西就沒必要放在渲染樹中了。
CSS 的 Rule Tree主要是爲了完成匹配並把CSS Rule附加上Rendering Tree上的每個Element。也就是DOM結點。也就是所謂的Frame。
然後,計算每個Frame(也就是每個Element)的位置,這又叫layout和reflow過程。
3)最後通過調用操作系統Native GUI的API繪製。== 如圖 ==
在這裏插入圖片描述
瀏覽器渲染過程大體分爲如下三部分

1)瀏覽器會解析三個東西:

  • 一是HTML/SVG/XHTML,HTML字符串描述了一個頁面的結構,瀏覽器會把HTML結構字符串解析轉換DOM樹形結構。
  • 二是CSS,解析CSS會產生CSS規則樹,它和DOM結構比較像。

在這裏插入圖片描述

  • 三是Javascript腳本,等到Javascript 腳本文件加載後, 通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree。

在這裏插入圖片描述
解析完成後,瀏覽器引擎會通過DOM Tree 和 CSS Rule Tree 來構造 Rendering Tree。

Rendering Tree 渲染樹並不等同於DOM樹,渲染樹只會包括需要顯示的節點和這些節點的樣式信息。
CSS 的 Rule Tree主要是爲了完成匹配並把CSS Rule附加上Rendering Tree上的每個Element(也就是每個Frame)。
然後,計算每個Frame 的位置,這又叫layoutreflow過程。

最後通過調用操作系統Native GUI的API繪製。


CSSOM 樹和 DOM 樹合併成渲染樹,然後用於計算每個可見元素的佈局,並輸出給繪製流程,將像素渲染到屏幕上。優化上述每一個步驟對實現最佳渲染性能至關重要。

在前面介紹構建對象模型的章節中,我們根據 HTML 和 CSS 輸入構建了 DOM 樹和 CSSOM 樹。 不過,它們都是獨立的對象,分別網羅文檔不同方面的信息:一個描述內容,另一個則是描述需要對文檔應用的樣式規則。我們該如何將兩者合併,讓瀏覽器在屏幕上渲染像素呢?
DOM 樹與 CSSOM 樹合併後形成渲染樹。
渲染樹只包含渲染網頁所需的節點。
佈局計算每個對象的精確位置和大小。
最後一步是繪製,使用最終渲染樹將像素渲染到屏幕上。
第一步是讓瀏覽器將 DOM 和 CSSOM 合併成一個“渲染樹”,網羅網頁上所有可見的 DOM 內容,以及每個節點的所有 CSSOM 樣式信息。
在這裏插入圖片描述
爲構建渲染樹,瀏覽器大體上完成了下列工作:

從 DOM 樹的根節點開始遍歷每個可見節點。

某些節點不可見(例如腳本標記、元標記等),因爲它們不會體現在渲染輸出中,所以會被忽略。
某些節點通過 CSS 隱藏,因此在渲染樹中也會被忽略,例如,上例中的 span 節點—不會出現在渲染樹中,—因爲有一個顯式規則在該節點上設置了 “display: none” 屬性。
對於每個可見節點,爲其找到適配的 CSSOM 規則並應用它們。

發射可見節點,連同其內容和計算的樣式。

Note: 簡單提一句,請注意 visibility: hidden 與 display: none 是不一樣的。前者隱藏元素,但元素仍佔據着佈局空間(即將其渲染成一個空框),而後者 (display: none) 將元素從渲染樹中完全移除,元素既不可見,也不是佈局的組成部分。

最終輸出的渲染同時包含了屏幕上的所有可見內容及其樣式信息。有了渲染樹,我們就可以進入“佈局”階段。

到目前爲止,我們計算了哪些節點應該是可見的以及它們的計算樣式,但我們尚未計算它們在設備視口內的確切位置和大小—這就是“佈局”階段,也稱爲“自動重排”。

爲弄清每個對象在網頁上的確切大小和位置,瀏覽器從渲染樹的根節點開始進行遍歷。讓我們考慮下面這樣一個簡單的實例:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path: Hello world!</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">Hello world!</div>
    </div>
  </body>
</html>

以上網頁的正文包含兩個嵌套 div:第一個(父)div 將節點的顯示尺寸設置爲視口寬度的 50%,—父 div 包含的第二個 div—將其寬度設置爲其父項的 50%;即視口寬度的 25%。
在這裏插入圖片描述
佈局流程的輸出是一個“盒模型”,它會精確地捕獲每個元素在視口內的確切位置和尺寸:所有相對測量值都轉換爲屏幕上的絕對像素。

最後,既然我們知道了哪些節點可見、它們的計算樣式以及幾何信息,我們終於可以將這些信息傳遞給最後一個階段:將渲染樹中的每個節點轉換成屏幕上的實際像素。這一步通常稱爲“繪製”或“柵格化”。

上述步驟都需要瀏覽器完成大量工作,所以相當耗時。不過,Chrome DevTools 可以幫助我們對上述所有三個階段進行深入的瞭解。讓我們看一下最初“hello world”示例的佈局階段:
在這裏插入圖片描述

  • Layout 事件在時間線中捕獲渲染樹構建以及位置和尺寸計算。
    佈局完成後,瀏覽器會立即發出“Paint Setup”和“Paint”事件,將渲染樹轉換成屏幕上的像素。
  • Layout 事件在時間線中捕獲渲染樹構建以及位置和尺寸計算。
    佈局完成後,瀏覽器會立即發出“Paint Setup”和“Paint”事件,將渲染樹轉換成屏幕上的像素。

執行渲染樹構建、佈局和繪製所需的時間將取決於文檔大小、應用的樣式,以及運行文檔的設備:文檔越大,瀏覽器需要完成的工作就越多;樣式越複雜,繪製需要的時間就越長(例如,單色的繪製開銷“較小”,而陰影的計算和渲染開銷則要“大得多”)。

最後將在視口中看到下面的網頁:
在這裏插入圖片描述

下面簡要概述了瀏覽器完成的步驟:

處理 HTML 標記並構建 DOM 樹。
處理 CSS 標記並構建 CSSOM 樹。
將 DOM 與 CSSOM 合併成一個渲染樹。
根據渲染樹來佈局,以計算每個節點的幾何信息。
將各個節點繪製到屏幕上。
我們的演示網頁看起來可能很簡單,實際上卻需要完成相當多的工作。如果 DOM 或 CSSOM 被修改,您只能再執行一遍以上所有步驟,以確定哪些像素需要在屏幕上進行重新渲染。

優化關鍵渲染路徑就是指最大限度縮短執行上述第 1 步至第 5 步耗費的總時間。 這樣一來,就能儘快將內容渲染到屏幕上,此外還能縮短首次渲染後屏幕刷新的時間,即爲交互式內容實現更高的刷新率。

阻塞渲染的 CSS

默認情況下,CSS 被視爲阻塞渲染的資源,這意味着瀏覽器將不會渲染任何已處理的內容,直至 CSSOM 構建完畢。請務必精簡您的 CSS,儘快提供它,並利用媒體類型和查詢來解除對渲染的阻塞。

在渲染樹構建中,我們看到關鍵渲染路徑要求我們同時具有 DOM 和 CSSOM 才能構建渲染樹。這會給性能造成嚴重影響:HTML 和 CSS 都是阻塞渲染的資源。 HTML 顯然是必需的,因爲如果沒有 DOM,我們就沒有可渲染的內容,但 CSS 的必要性可能就不太明顯。如果我們在 CSS 不阻塞渲染的情況下嘗試渲染一個普通網頁會怎樣?

  • 默認情況下,CSS 被視爲阻塞渲染的資源。
  • 我們可以通過媒體類型和媒體查詢將一些 CSS 資源標記爲不阻塞渲染。
  • 瀏覽器會下載所有 CSS 資源,無論阻塞還是不阻塞。
    使用css 的紐約時報未使用 css
    上例展示了紐約時報網站使用和不使用 CSS 的顯示效果,它證明了爲何要在 CSS 準備就緒之前阻塞渲染,—沒有 CSS 的網頁實際上無法使用。右側的情況通常稱爲“內容樣式短暫失效”(FOUC)。瀏覽器將阻塞渲染,直至 DOM 和 CSSOM 全都準備就緒。

CSS 是阻塞渲染的資源。需要將它儘早、儘快地下載到客戶端,以便縮短首次渲染的時間。

過,如果我們有一些 CSS 樣式只在特定條件下(例如顯示網頁或將網頁投影到大型顯示器上時)使用,又該如何?如果這些資源不阻塞渲染,該有多好。

我們可以通過 CSS“媒體類型”和“媒體查詢” 來解決這類用例:

<link href="style.css" rel="stylesheet">
<link href="print.css" rel="stylesheet" media="print">
<link href="other.css" rel="stylesheet" media="(min-width: 40em)">

媒體查詢由媒體類型以及零個或多個檢查特定媒體特徵狀況的表達式組成。例如,上面的第一個樣式表聲明未提供任何媒體類型或查詢,因此它適用於所有情況,也就是說,它始終會阻塞渲染。第二個樣式表則不然,它只在打印內容時適用—或許您想重新安排佈局、更改字體等等,因此在網頁首次加載時,該樣式表不需要阻塞渲染。最後,最後一個樣式表聲明提供由瀏覽器執行的“媒體查詢”:符合條件時,瀏覽器將阻塞渲染,直至樣式表下載並處理完畢。

通過使用媒體查詢,我們可以根據特定用例(比如顯示或打印),也可以根據動態情況(比如屏幕方向變化、尺寸調整事件等)定製外觀。聲明您的樣式表資產時,請密切注意媒體類型和查詢,因爲它們將嚴重影響關鍵渲染路徑的性能。

讓我們考慮下面這些實例:

<link href="style.css"    rel="stylesheet">
<link href="style.css"    rel="stylesheet" media="all">
<link href="portrait.css" rel="stylesheet" media="orientation:portrait">
<link href="print.css"    rel="stylesheet" media="print">
  • == 第一個聲明阻塞渲染,適用於所有情況。==
  • == 第二個聲明同樣阻塞渲染:“all”是默認類型,如果您不指定任何類型,則隱式設置爲“all”。因此,第一個聲明和第二個聲明實際上是等效的。==
  • 第三個聲明具有動態媒體查詢,將在網頁加載時計算。根據網頁加載時設備的方向,portrait.css 可能阻塞渲染,也可能不阻塞渲染。
  • 最後一個聲明只在打印網頁時應用,因此網頁首次在瀏覽器中加載時,它不會阻塞渲染。
  • 最後,請注意“阻塞渲染”僅是指瀏覽器是否需要暫停網頁的首次渲染,直至該資源準備就緒。無論哪一種情況,瀏覽器仍會下載 CSS 資產,只不過不阻塞渲染的資源優先級較低罷了。

性能優化策略

基於上面介紹的瀏覽器渲染原理,DOM 和 CSSOM 結構構建順序,初始化可以對頁面渲染做些優化,提升頁面性能。

JS優化:

<script> 標籤加上 defer屬性 和 async屬性 用於在不阻塞頁面文檔解析的前提下,控制腳本的下載和執行。

defer屬性: 用於開啓新的線程下載腳本文件,並使腳本在文檔解析完成後執行。
async屬性: HTML5新增屬性,用於異步下載腳本文件,下載完畢立即解釋執行代碼。

CSS優化: <link> 標籤的 rel屬性 中的屬性值設置爲 preload 能夠讓你在你的HTML頁面中可以指明哪些資源是在頁面加載完成後即刻需要的,最優的配置加載順序,提高渲染性能

總結

綜上所述,我們得出這樣的結論:

  • 瀏覽器工作流程:構建DOM -> 構建CSSOM -> 構建渲染樹 -> 佈局 -> 繪製。
  • CSSOM 會阻塞渲染只有當CSSOM構建完畢後纔會進入下一個階段構建渲染樹。
  • 通常情況下DOM和CSSOM是並行構建的但是當瀏覽器遇到一個不帶defer或async屬性的script標籤時,DOM構建將暫停,如果此時又恰巧瀏覽器尚未完成CSSOM的下載和構建,由於JavaScript可以修改CSSOM,所以需要等CSSOM構建完畢後再執行JS,最後才重新DOM構建。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章