前端開發者應知必會:瀏覽器是如何渲染網頁的

原文鏈接:What Every Frontend Developer Should Know About Webpage Rendering

作者:Alexander Skutin

譯者:余博倫

轉載請註明出處。

今天我們討論的話題將專註於網頁渲染以及它在Web開發中至關重要的作用。其實網上已經有許多談論這個主題的文章了,但大多數文章提供的都是比較碎片化的信息,我需要查閱相當多的資料,才能完整地了解網頁渲染。所以我決定寫下這篇有一定綜合性的文章。相信一方面能夠幫助初學者了解網頁渲染的原理,另一方面也能幫助有經驗的同學細化鞏固相關的知識結構。

不同的瀏覽器引擎運行起來會有些許差異,針對特定瀏覽器的具體內容會更加複雜。本文並不會涉及某個瀏覽器的底層原理,而是討論一些通共的原則。

瀏覽器如何渲染網頁

我們先來了解一下瀏覽器是如何對網頁進行渲染的:

  1. 瀏覽器將從伺服器獲取的HTML文檔構建成文檔對象模型DOM(Document Object Model).
  2. 樣式將被載入和解析,構成層疊樣式表模型CSSOM(CSS Object Model).
  3. 在DOM和CSSOM之上,渲染樹(rendering tree)將會被創建,代表一系列將被渲染的對象(這在Webkit內核中被稱為renderer或者渲染對象render object,在Gecko內核中被稱為框架frame)。渲染樹映射除了不可見元素(例如<head>或者含有display:none;的標籤)外的所有DOM結構。每一段文本字元串都將劃分在不同的渲染對象中,每一個渲染對象都包含了它相應的DOM對象以及計算後的樣式。換句話講,渲染樹是DOM的直觀表示。
  4. 渲染樹的每個元素包含的內容都是計算過的,它被稱之為布局layout.瀏覽器使用一種流式處理的方法,只需要一次pass繪製操作就可以布局所有的元素(tables需要多次pass繪製,pass表示像素處理和頂點處理)。
  5. 最後布局完成,渲染樹將轉化為屏幕上的實際內容,這一步被稱為繪製painting.

重繪Repaint

當頁面元素樣式的改變不影響元素在文檔流中的位置時(例如background-color, border-color,visibility),瀏覽器只會將新樣式賦予元素並進行重繪操作。

迴流Reflow

當改變影響文檔內容或者結構,或者元素位置時,迴流操作就會被觸發,一般有以下幾種情況:

  • DOM操作(對元素的增刪改,順序變化等);
  • 內容變化,包括表單區域內的文本改變;
  • CSS屬性的更改或重新計算;
  • 增刪樣式表內容;
  • 修改class屬性;
  • 瀏覽器窗口變化(滾動或縮放);
  • 偽類樣式激活(:hover等)。

瀏覽器如何優化渲染

瀏覽器本身會儘可能地減少其重繪或迴流的次數,只更改必要的元素。例如一個position設置為absolute/fixed的元素的更改只會影響其本身和其子元素,而static的元素變化則會影響其之後的所有頁面元素。

另外一項優化的技術則是在JavaScript代碼運行時,瀏覽器會緩存所有的變化,然後只通過一次pass繪製操作來應用這些更改。例如下面這段代碼只會觸發一次重繪和迴流:

var $body = $(body);n$body.css(padding, 1px); // 觸發重繪與迴流n$body.css(color, red); // 觸發重繪n$body.css(margin, 2px); // 觸發重繪與迴流n// 最終只有一次重繪和迴流被觸發n

然而,根據我們之前提到過的,獲取某個元素的屬性將會觸發強制迴流。比如我們在剛才的代碼中加上一句讀取元素屬性的操作:

var $body = $(body);n$body.css(padding, 1px);n$body.css(padding); // 此處觸發強制迴流n$body.css(color, red);n$body.css(margin, 2px);n

結果就會有兩次迴流發生。因此,我們應該盡量合併讀取元素屬性的操作來優化性能。

當然也有我們不得不觸發強制迴流的情況。比如說對同一個元素的margin-left屬性進行兩次操作——開始的時候賦值100px的距離,之後為了實現動畫效果,再加上transition屬性將距離改變到50px.

我們先定義一個CSS類:

.has-transition {n -webkit-transition: margin-left 1s ease-out;n -moz-transition: margin-left 1s ease-out;n -o-transition: margin-left 1s ease-out;n transition: margin-left 1s ease-out;n}n

之後再對頁面元素進行操作:

// 我們的元素開始默認含有 "has-transition" 的class屬性nvar $targetElem = $(#targetElemId);nn// 移除默認的 "has-transition"n$targetElem.removeClass(has-transition);nn// 此處的屬性改變沒有動畫效果n$targetElem.css(margin-left, 100);nn// 再加上原來的屬性名n$targetElem.addClass(has-transition);nn// 這次改變有動畫效果n$targetElem.css(margin-left, 50);n

但事實上這段代碼並不會像注釋描述的那樣運作,每條語句的操作將被緩存,只有結果會在頁面上顯示,所以我們就需要手動進行一次強制迴流:

// 移除默認的 "has-transition"n$(this).removeClass(has-transition);nn// 此處的屬性改變沒有動畫效果n$(this).css(margin-left, 100);nn// 觸發強制迴流,上述兩條語句的效果會馬上在頁面中顯示n$(this)[0].offsetHeight; // 只是舉個例子,別的觸發方法也可以nn// 再加上原來的屬性名n$(this).addClass(has-transition);nn// 這次改變有動畫效果n$(this).css(margin-left, 50);n

你可以在JSBin預覽這個例子。

優化渲染效率的幾條最佳實踐

根據我查閱的一些資料,總結出以下幾條優化建議:

  • 合法地書寫HTML和CSS,不要忘了文檔編碼類型。樣式文件應當在 <head> 標籤中,腳本文件在 <body> 結束前。
  • 簡化並優化你的CSS選擇器(有些人可能CSS預處理器用習慣了從來不關注這一點)。將嵌套層減少到最小。CSS選擇器根據其優先順序具有不同的運行效率(從快到慢):
    • ID選擇器: #id
    • 類選擇器: .class
    • 標籤選擇器: div
    • 相鄰選擇器: a + i
    • 子元素選擇器: ul > li
    • 通用選擇器: *
    • 屬性選擇器: input[type="text"]
    • 偽類選擇器: a:hover

瀏覽器中CSS選擇器是從右到左進行匹配的(為什麼瀏覽器要從右到左匹配樣式選擇器),這也是為什麼越短的選擇器運行越快的原因(別提通用選擇器,它會遍歷所有元素):

div * {...} // ×n.list li {...} // ×n.list-item {...} // n#list .list-item {...} // n

  • 在你的腳本代碼中,盡量減少DOM操作。緩存所有的內容,包括屬性和對象(如果他們需要被複用的話)。盡量將元素緩存到本地之後再進行操作,最後再添加到DOM當中。
  • 如果你使用jQuery進行DOM操作的話,最好遵循jQuery最佳實踐。
  • 修改元素樣式時,更改其class屬性是性能最高的方法。你的選擇器越有針對性越好(這同樣也有助於分離頁面樣式和邏輯)。
  • 盡量只對 positionabsolute/fixed 的元素設置動畫。
  • 在頁面滾動時禁用 :hover 樣式效果:

.disable-hover {n pointer-events: none;n}n

var body = document.body,n timer;nnwindow.addEventListener(scroll, function() {n clearTimeout(timer);n if(!body.classList.contains(disable-hover)) {n body.classList.add(disable-hover)n }n n timer = setTimeout(function(){n body.classList.remove(disable-hover)n },500);n}, false);n

如果你想對此話題進行更深入的了解,可以查閱:

  1. How browsers work 中文版
  2. 【譯】瀏覽器渲染:repaint,reflow/relayout,restyle

有任何好的意見或者是建議以及心得體會歡迎在評論區參與討論。

你也可以加入本專欄QQ群一起交流學習。

歡迎加入從零學習前端開發,群號碼:591950591

更加歡迎將你的原創或翻譯文章投稿至本專欄。


推薦閱讀:

「每日一題」CSRF 是什麼?
從零學習前端開發·HTML
你可能不知道的 css 內容塊
Webpack 速成
[翻譯]React還是Vue:你該如何選擇?

TAG:网页浏览器 | 前端入门 | Web开发 |