天天看點

淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

1 大緻流程

淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

浏覽器端會把HTML解析成DOM樹,CSS檔案解析成CSSOM =》 兩者解析完成後 DOM 和 CSSOM合并 =》 浏覽器會進一步解析為Render Tree =》 浏覽器計算每個元素在頁面上位置和大小(Layout) =》 最後繪制到頁面上 (Paint )

2 步驟詳解

2.1 解析html

輸入網址後,浏覽器發起請求,首先得到的是html文檔,當我們的浏覽器獲得html檔案後,浏覽器會自上而下的加載,通過HTML Parser解析器對html文檔進行解析。

加載說的就是擷取資源檔案的過程,如果在加載的過程中,遇到外部css檔案和圖檔,浏覽器會另外發出一個請求,來擷取css檔案和相應的圖檔,這個請求是異步的,并不會影響html檔案。

但是如果遇到javascript檔案,html檔案會挂起渲染的線程,等待javascript加載完畢後,html檔案再繼續渲染。

html代碼中是如何生成DOM樹的:

用如下代碼示範生成DOM樹的過程:

<html>
	<body>
		<h1>HelloWorld</h1>
		<div>
			<div>
				<p>picture:</p>
				<img src="example.png"/>
			</div>
      <div>
				<p>A paragraph of explanatory text...</p>
			</div>
		</div>
	</body>
</html>
           

首先樹建構器接收到标簽解析器發來的起始标簽名後,會加入到棧中,圖1是解析到

<h1>

标簽的棧中壓入的内容,共有

<html><body><h1>

三個标簽,此時還未向DOM樹中添加任何結點(圖中黑色實線框代表開始标簽,紅色虛線框代表結束标簽,結束标簽不會入棧)。

淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

繼續向下解析,接收到一個結束标簽,此時查詢棧頂元素,如果和傳入的結束标簽屬于同種類型的p标簽(如圖2),則将棧頂元素彈出,向DOM樹中加入此節點,然後繼續向下解析(如圖3)。

如果遇到的是沒有封閉标簽的元素如,則直接加入DOM樹中即可,無需入棧。

依次向下解析,當棧為空,即根節點也加入到DOM樹中,DOM樹建構完畢。

當某個元素缺失結束标簽時,會怎麼樣?浏覽器會自動補一個結束标簽。

2.2 css解析

遇到内聯的css樣式就開始解析,外部的css檔案,則在接收到了之後通過CSS Parse解析器進行解析。最終把css語句解析成為CSSOM(CSS Object Model)。

在解析css的同時也在解析dom,兩者是并發的,是以等到css解析完畢就會逐漸的渲染頁面了。

當所有的css檔案下載下傳完且所有的CSSOM建構結束後,就會和DOM一起生成Render Tree。

是以,頁面渲染依賴于css的加載。

為了形成Render Tree,浏覽器大緻做的事情有:

從DOM樹根節點開始,周遊每一個可見的節點

一些節點是完全不可見的(比如 script标簽,meta标簽等),這些節點會被忽略,因為他們不會影響渲染的輸出

一些節點是通過CSS樣式隐藏了,這些節點同樣被忽略——樣式是display:none;

對每一個可見的節點,找到合适的比對的CSSOM規則,并且應用樣式

注意:“visibility:hidden”和“display:none”是不同的,“visibility:hidden”将元素設定為不可見,但是同樣在布局上占領一定空間(例如,它會被渲染成為空盒子),但是“display:none”的元素不會挂載到render tree上,不是布局中的一部分 。

可參考下圖對比其關系:

淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

2.3 js解析

html加載過程中 ,遇到js就立即解析下載下傳執行。

當浏覽器遇到一個 script 标記時,DOM 建構将暫停,直至腳本完成執行,然後繼續建構DOM。每次去執行JavaScript腳本都會嚴重地阻塞DOM樹的建構,如果JavaScript腳本還操作了CSSOM,而正好這個CSSOM還沒有下載下傳和建構,浏覽器甚至會延遲腳本執行和建構DOM,直至完成其CSSOM的下載下傳和建構。

為什麼html需要等待javascript呢?因為javascript可能會修改DOM,導緻後續的html資源白白加載,是以html必須等待javascript檔案加載完畢後,再繼續渲染。這也就是為什麼javascript檔案要寫在底部body标簽前的原因。

是以,script 标簽的位置很重要。實際使用時,可以遵循下面兩個原則:

CSS 優先:引入順序上,CSS 資源先于 JavaScript 資源。

JS置後:我們通常把JS代碼放到頁面底部,且JavaScript 應盡量少影響 DOM 的建構。

如果是外部的,還可以在

<script>

标簽上加上defer或async屬性

  • defer可以讓js的下載下傳不影響html的後續解析,且在html解析完了再執行js檔案,且按照原來的下載下傳順序
  • async也是讓js的下載下傳不影響html的後續解析,但一旦下載下傳完了就立即執行,是以也無法保證按照下載下傳順序執行

css解析和js解析時互斥的,css阻塞js的執行,但是不會阻塞外部腳本的加載。

js邏輯對于dom節點的依賴關系,因為js可能會影響到render tree,為了避免浪費浏覽器資源,執行js代碼時,dom和css解析都會被當機,js執行完了,才能執行後續的内容。

但是,當JS執行時間過長會頁面阻塞,那麼JS就真的對CPU密集型計算無能為力麼?

是以,後來HTML5中支援了 Web Worker。

通過使用Web Workers,Web應用程式可以在獨立于主線程的背景線程中,運作一個腳本操作。這樣做的好處是可以在獨立線程中執行費時的處理任務,進而允許主線程(通常是UI線程)不會是以被阻塞/放慢。

2.4 回流

概念:當render tree中的一部分(或全部)因為元素的規模尺寸,布局,隐藏等改變而需要重新建構。這就稱為回流(reflow)

當頁面布局、元素幾何屬性發生變化時 就會發生回流,此時我們需要重新驗證并計算渲染樹。是以如何回流過于頻繁,也就會增加浏覽器的工作量,降低了浏覽器性能。

觸發頁面重布局的屬性

  • 盒子模型相關屬性會觸發重布局
  • 定位屬性及浮動也會觸發重布局
  • 改變節點内部文字結構也會觸發重布局
    淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

2.5 重繪

概念:當render tree中的一些元素需要更新屬性,而這些屬性隻是影響元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪。

隻觸發重繪的屬性

淺談浏覽器渲染1 大緻流程2 步驟詳解3 性能優化

關系:當重繪時不一定會發生回流,但是回流時一定是需要對頁面進行重繪。

3 性能優化

從性能優化的角度,減少浏覽器的計算,盡量減少浏覽器的重排和重繪,尤其是重排。

  1. 避免使用通配規則。如 *{} 計算次數驚人!隻對需要用到的元素進行選擇。
  2. 避免設定大量的style屬性,因為通過設定style屬性改變結點樣式的話,每一次設定都會觸發一次reflow,是以最好是使用class屬性。
  3. 實作元素的動畫,它的position屬性,最好是設為absoulte或fixed,這樣不會影響其他元素的布局。
  4. 動畫實作的速度的選擇。比如實作一個動畫,以1個像素為機關移動這樣最平滑,但是reflow就會過于頻繁,大量消耗CPU資源,如果以3個像素為機關移動則會好很多。
  5. 不要使用table布局,因為table中某個元素旦觸發了reflow,那麼整個table的元素都會觸發reflow。那麼在不得已使用table的場合,可以設定table-layout:auto;或者是table-layout:fixed這樣可以讓table一行一行的渲染,這種做法也是為了限制reflow的影響範圍。
  6. 如果CSS裡面有計算表達式,每次都會重新計算一遍,觸發一次reflow

部分摘自:

HTML文檔解析和DOM樹的建構

借助performance工具直覺了解浏覽器的渲染過程