1肥败、瀏覽器解析過程
瀏覽器渲染頁面前需要先構建 DOM 和 CSSOM 樹。因此辖试,我們需要確保盡快將 HTML 和 CSS 都提供給瀏覽器帅涂。
- 字節(jié) → 字符 → 令牌 → 節(jié)點 → 對象模型。
- HTML 標記轉換成文檔對象模型 (DOM)疙咸;CSS 標記轉換成 CSS 對象模型 (CSSOM)县匠。
- DOM 和 CSSOM 是獨立的數(shù)據(jù)結構。
- Chrome DevTools Timeline 讓我們可以捕獲和檢查 DOM 和 CSSOM 的構建和處理開銷。
1.1 文檔對象模型 (DOM)
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="style.css" rel="stylesheet">
<title>Critical Path</title>
</head>
<body>
<p>Hello <span>web performance</span> students!</p>
<div><img src="awesome-photo.jpg"></div>
</body>
</html>
讓我們從可能的最簡單情況入手:一個包含一些文本和一幅圖片的普通 HTML 頁面聚唐。瀏覽器如何處理此頁面丐重?
- 轉換: 瀏覽器從磁盤或網(wǎng)絡讀取 HTML 的原始字節(jié),并根據(jù)文件的指定編碼(例如 UTF-8)將它們轉換成各個字符杆查。
- 令牌化: 瀏覽器將字符串轉換成 W3C HTML5 標準規(guī)定的各種令牌扮惦,例如,“<html>”亲桦、“<body>”崖蜜,以及其他尖括號內(nèi)的字符串。每個令牌都具有特殊含義和一組規(guī)則客峭。
- 詞法分析: 發(fā)出的令牌轉換成定義其屬性和規(guī)則的“對象”豫领。
-
DOM 構建: 最后,由于 HTML 標記定義不同標記之間的關系(一些標記包含在其他標記內(nèi))舔琅,創(chuàng)建的對象鏈接在一個樹數(shù)據(jù)結構內(nèi)等恐,此結構也會捕獲原始標記中定義的父項-子項關系:HTML 對象是 body 對象的父項,body 是 paragraph 對象的父項备蚓,依此類推课蔬。
整個流程的最終輸出是我們這個簡單頁面的文檔對象模型 (DOM),瀏覽器對頁面進行的所有進一步處理都會用到它郊尝。
DOM 樹捕獲文檔標記的屬性和關系二跋,但并未告訴我們元素在渲染后呈現(xiàn)的外觀。那是 CSSOM 的責任流昏。
1.2 CSS 對象模型 (CSSOM)
在瀏覽器構建我們這個簡單頁面的 DOM 時扎即,在文檔的 head 部分遇到了一個 link 標記,該標記引用一個外部 CSS 樣式表:style.css况凉。由于預見到需要利用該資源來渲染頁面谚鄙,它會立即發(fā)出對該資源的請求,并返回以下內(nèi)容:
body { font-size: 16px }
p { font-weight: bold }
span { color: red }
p span { display: none }
img { float: right }
與處理 HTML 時一樣茎刚,我們需要將收到的 CSS 規(guī)則轉換成某種瀏覽器能夠理解和處理的東西襟锐。因此撤逢,我們會重復 HTML 過程膛锭,不過是為 CSS 而不是 HTML:
CSS 字節(jié)轉換成字符,接著轉換成令牌和節(jié)點蚊荣,最后鏈接到一個稱為“CSS 對象模型”(CSSOM) 的樹結構內(nèi):
CSSOM 為何具有樹結構初狰?為頁面上的任何對象計算最后一組樣式時,瀏覽器都會先從適用于該節(jié)點的最通用規(guī)則開始(例如互例,如果該節(jié)點是 body 元素的子項奢入,則應用所有 body 樣式),然后通過應用更具體的規(guī)則(即規(guī)則“向下級聯(lián)”)以遞歸方式優(yōu)化計算的樣式媳叨。
以上面的 CSSOM 樹為例進行更具體的闡述腥光。span 標記內(nèi)包含的任何置于 body 元素內(nèi)的文本都將具有 16 像素字號关顷,并且顏色為紅色 — font-size 指令從 body 向下級聯(lián)至 span。不過武福,如果某個 span 標記是某個段落 (p) 標記的子項议双,則其內(nèi)容將不會顯示。
請注意捉片,以上樹并非完整的 CSSOM 樹平痰,它只顯示了我們決定在樣式表中替換的樣式。每個瀏覽器都提供一組默認樣式(也稱為“User Agent 樣式”)伍纫,即我們不提供任何自定義樣式時所看到的樣式宗雇,我們的樣式只是替換這些默認樣式。
2莹规、渲染樹構建赔蒲、布局及繪制
- DOM 樹與 CSSOM 樹合并后形成渲染樹。
- 渲染樹只包含渲染網(wǎng)頁所需的節(jié)點良漱。
- 布局計算每個對象的精確位置和大小嘹履。
- 最后一步是繪制,使用最終渲染樹將像素渲染到屏幕上债热。
第一步是讓瀏覽器將 DOM 和 CSSOM 合并成一個“渲染樹”砾嫉,網(wǎng)羅網(wǎng)頁上所有可見的 DOM 內(nèi)容,以及每個節(jié)點的所有 CSSOM 樣式信息窒篱。
[圖片上傳失敗...(image-a2ea6d-1545730467906)]
為構建渲染樹焕刮,瀏覽器大體上完成了下列工作:
- 從 DOM 樹的根節(jié)點開始遍歷每個可見節(jié)點。
- 某些節(jié)點不可見(例如腳本標記墙杯、元標記等)配并,因為它們不會體現(xiàn)在渲染輸出中,所以會被忽略高镐。
- 某些節(jié)點通過 CSS 隱藏溉旋,因此在渲染樹中也會被忽略,例如嫉髓,上例中的 span 節(jié)點---不會出現(xiàn)在渲染樹中观腊,---因為有一個顯式規(guī)則在該節(jié)點上設置了“display: none”屬性。
- 對于每個可見節(jié)點算行,為其找到適配的 CSSOM 規(guī)則并應用它們梧油。
- 發(fā)射可見節(jié)點,連同其內(nèi)容和計算的樣式州邢。
Note: 簡單提一句儡陨,請注意 visibility: hidden 與 display: none 是不一樣的。前者隱藏元素,但元素仍占據(jù)著布局空間(即將其渲染成一個空框)骗村,而后者 (display: none) 將元素從渲染樹中完全移除嫌褪,元素既不可見,也不是布局的組成部分胚股。
最終輸出的渲染同時包含了屏幕上的所有可見內(nèi)容及其樣式信息渔扎。有了渲染樹,我們就可以進入“布局”階段信轿。
到目前為止晃痴,我們計算了哪些節(jié)點應該是可見的以及它們的計算樣式,但我們尚未計算它們在設備視口內(nèi)的確切位置和大小---這就是“布局”階段财忽,也稱為“自動重排”倘核。
為弄清每個對象在網(wǎng)頁上的確切大小和位置,瀏覽器從渲染樹的根節(jié)點開始進行遍歷即彪。讓我們考慮下面這樣一個簡單的實例:
<!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>
以上網(wǎng)頁的正文包含兩個嵌套 div:第一個(父)div 將節(jié)點的顯示尺寸設置為視口寬度的 50%紧唱,---父 div 包含的第二個 div---將其寬度設置為其父項的 50%;即視口寬度的 25%隶校。
布局流程的輸出是一個“盒模型”漏益,它會精確地捕獲每個元素在視口內(nèi)的確切位置和尺寸:所有相對測量值都轉換為屏幕上的絕對像素。
最后深胳,既然我們知道了哪些節(jié)點可見绰疤、它們的計算樣式以及幾何信息,我們終于可以將這些信息傳遞給最后一個階段:將渲染樹中的每個節(jié)點轉換成屏幕上的實際像素舞终。這一步通常稱為“繪制”或“柵格化”轻庆。
執(zhí)行渲染樹構建、布局和繪制所需的時間將取決于文檔大小敛劝、應用的樣式余爆,以及運行文檔的設備:文檔越大,瀏覽器需要完成的工作就越多夸盟;樣式越復雜蛾方,繪制需要的時間就越長(例如,單色的繪制開銷“較小”上陕,而陰影的計算和渲染開銷則要“大得多”)桩砰。
最后將在視口中看到網(wǎng)頁。
總結瀏覽器完成的步驟:
- 處理 HTML 標記并構建 DOM 樹唆垃。
- 處理 CSS 標記并構建 CSSOM 樹五芝。
- 將 DOM 與 CSSOM 合并成一個渲染樹。
- 根據(jù)渲染樹來布局辕万,以計算每個節(jié)點的幾何信息。
- 將各個節(jié)點繪制到屏幕上。
以上流程看起來可能很簡單渐尿,實際上卻需要完成相當多的工作醉途。如果 DOM 或 CSSOM 被修改,您只能再執(zhí)行一遍以上所有步驟砖茸,以確定哪些像素需要在屏幕上進行重新渲染隘擎。
網(wǎng)頁優(yōu)化關鍵渲染路徑 就是指最大限度縮短執(zhí)行上述第 1 步至第 5 步耗費的總時間。 這樣一來凉夯,就能盡快將內(nèi)容渲染到屏幕上货葬,此外還能縮短首次渲染后屏幕刷新的時間,即為交互式內(nèi)容實現(xiàn)更高的刷新率劲够。
tips:為什么將css放在頁面前面引入:
這樣會先加載css的樣式震桶,在渲染dom的時候已經(jīng)知道了自己的樣式了,所以一次渲染成功征绎;
如果css放在底部蹲姐,那么需要先渲染dom,然后加載css后會重新渲染之前dom人柿,需要兩次渲染柴墩。