當用戶輸入網頁路徑,瀏覽器首先通過網路請求拿到html字元串,然後經過HTML解析、樣式計算、佈局、分層、繪製、分塊、光柵化、畫等過程,將頁面的內容最終呈現到了屏幕上。 ...
渲染是指什麼?
渲染 (render),是指將HTML
代碼轉換為像素信息的過程。
當用戶在瀏覽器上輸入url之後,訪問的伺服器返回html文件,本質上是html代碼,是字元串。渲染這個過程的任務就是:識別這段字元串,並且轉換為像素信息。
渲染時間點
用戶打開網頁的過程可以簡單概括為:
-
網路:拿HTML。
這裡概括為拿HTML,是因為在HTML文件中可以通過
<style>
標簽和<script>
標簽引入 CSS 和 JS 文件。事實上網路的過程也很複雜, 但是不是這篇筆記的重點討論內容。
-
渲染:解析HTML代碼並最終轉換為像素信息。
瀏覽器有很多進程,其中有網路進程,而網路進程又包含網路線程。
網路線程完成網路請求任務之後,拿到了一個html
文件,但是它沒有解析的能力,於是將html
文件包裝成一個任務,通過消息隊列,轉交給渲染主線程。
渲染主線程拿到渲染任務之後,就開始了渲染流程,就是本篇筆記的重點內容。
渲染流程
解析HTML - Parse HTML
DOM樹(Document Object Model):頁面中的元素和文本,以樹形結構相關聯。
在 JS 代碼中,通過document
對象可以訪問和修改DOM樹。而上圖中的DOM樹指的是瀏覽器底層由C++
生成的DOM樹。
這一個轉換步驟是為後續步驟做準備的,因為字元串難處理,而對象結構容易處理。
CSS也會被解析成CSSOM(CSS Object Model),也是樹形結構,根節點(StyleSheetList)是網頁中所有的樣式表,二級子節點可能包含內部樣式表、外部樣式表、內聯樣式表和瀏覽器預設樣式表(取決於代碼中是否有這些內容),如果有兩個<link>
,則會出現兩個外部樣式表節點。
可以在github上的chromium源碼找到瀏覽器預設樣式表的內容。
除了瀏覽器預設樣式表,內部樣式表、外部樣式表、內聯樣式表都可以通過 JS 訪問到。
- 內部樣式表和外部樣式表:使用
document.styleSheets
可以訪問到一個數組,元素是樣式表對象。
- 使用
document.styleSheets[0].addRule("div", "border: 1px solid red important")
可以讓頁面上的所有div標簽的邊框變成紅色,這種做法與傳統的“獲取所有div標簽,再設置其style”的做法不同。- 內聯樣式表:使用
dom.style
訪問
HTML解析過程遇到CSS怎麼辦?
為了提高解析效率,瀏覽器會啟動一個預解析器率先下載和解析CSS。
渲染主線程在解析HTML的時候,會關註每一個標簽;而預解析線程只關註外部樣式表的標簽<link>
,儘快地完成CSS的下載與解析。
這樣做的目的是防止CSS的解析阻塞了HTML的解析。
HTML解析過程遇到JS怎麼辦?
渲染主線程遇到 JS 的script標簽時必須暫停一切行為,等待下載 JS 文件,並且啟用V8引擎解析執行 JS 代碼,然後才能繼續解析 HTML。
原因:JS 代碼可能修改 DOM 樹。
預解析線程可以分擔一點下載 JS 的任務。
樣式計算 - Recalculate Style
樣式計算過程計算每一個DOM節點的最終樣式(Computed Style)。
計算樣式如何查看:在瀏覽器上打開開發者工具,查看“計算樣式”,並選擇“全部顯示”。
通過上一過程,得到的 DOM 樹和 CSSOM 樹。通過遍歷 DOM 樹,為每一個 DOM 節點,計算它的所有 CSS 屬性。
屬性值的計算過程,分為如下4個步驟:
- 確定聲明值;
- 層疊衝突;
- 使用繼承;
- 使用預設值。
確定聲明值
如果先不考慮衝突的話,那麼通過 頁面作者書寫的CSS樣式 和 用戶代理樣式表(瀏覽器內置的樣式表) 的聲明值相加得到全部的聲明值,並且將部分值進行轉換。
例如,將color: red;
轉換為color: rgb(255, 0, 0);
,將font-size: 2em;
轉換為font-size: 14px;
。
層疊衝突
在確定聲明值時,可能出現一種情況,那就是聲明的樣式規則發生了衝突。
此時會進入解決層疊衝突的流程。而這一步又可以細分為下麵這三個步驟:
- 比較源的重要性
- 比較優先順序
- 比較次序
比較源的重要性
樣式有三種來源:
- 瀏覽器會有一個基本的樣式表來給任何網頁設置預設樣式。這些樣式統稱用戶代理樣式。
- 網頁的作者可以定義文檔的樣式,這是最常見的樣式表,稱之為頁面作者樣式。
- 瀏覽器的用戶,可以使用自定義樣式表定製使用體驗,稱之為用戶樣式。
對應的重要性順序依次為:頁面作者樣式 > 用戶樣式 > 用戶代理樣式。
可以在 MDN 中找到更詳細的說明:CSS 層疊 - CSS:層疊樣式表 | MDN (mozilla.org)
比較優先順序
如果在同一源中出現了樣式聲明衝突,則比較其優先順序。
簡單來說就是:ID選擇器 > 類名選擇器 > 標簽選擇器。
更詳細的說明可以查閱 MDN 的文章:優先順序 - CSS:層疊樣式表 | MDN (mozilla.org)
比較次序
如果出現同源同權重的情況,則比較樣式的聲明次序。
後聲明的樣式會覆蓋先聲明的樣式。
p{
/* 會被覆蓋 */
color: red;
}
p{
/* 生效 */
color: green;
}
顯然,不存在次序相同的情況。至此,樣式聲明中存在衝突的所有情況都解決了。
使用繼承
上文提到了,對於每一個 DOM 節點,都會去計算它的所有 CSS 屬性。
層疊衝突這一步驟完成之後,聲明值已全部確定。
而對於未聲明的屬性,並不是直接使用預設值,而是使用繼承值。
例如:
<div>
<p>hello world</p>
</div>
div{
color: red;
}
這裡<p>
標簽會繼承來自<div>
的color: red
樣式。
繼承原則:
-
繼承誰的?答:就近原則,誰近就繼承誰的,與權重無關。
-
哪些屬性能夠繼承?答:大部分字體相關的屬性都是可繼承的,可以在MDN上查找屬性是否可繼承。
使用預設值
如果經過上述過程仍不能確定屬性值,則使用預設值。
佈局 - Layout
根據 DOM 樹里每個節點的樣式,計算出每個節點的尺寸和位置。
有一些數值,例如:百分比,或者
auto
,在上一步驟無法算出來,在佈局這個過程才能算出來。
對於一個元素來說,它的尺寸和位置經常與它的包含塊(containing block)有關。
這裡簡單地記錄包含塊的知識,更詳細的說明可以查閱 MDN 文檔: