瀏覽器的渲染原理簡介

看到這個標題你們必定會想到這篇神文《How Browsers Work》,這篇文章把瀏覽器的不少細節講得很細,並且也被翻譯成了中文。爲何我還想寫一篇呢?由於兩個緣由,php

1)這篇文章太長了,閱讀成本太大,不能一口氣讀完。css

2)花了大力氣讀了這篇文章後能夠了解不少,但彷佛對工做沒什麼幫助。html

因此,我準備寫下這篇文章來解決上述兩個問題。但願你能在上班途中,或是坐馬桶時就能讀完,並能從中學會一些能用在工做上的東西。web

瀏覽器工做大流程

廢話少說,先來看個圖:ajax

從上面這個圖中,咱們能夠看到那麼幾個事:算法

 

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

  • 一個是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繪製。app

DOM解析

HTML的DOM Tree解析以下:less

1
2
3
4
5
6
7
8
9
10
11
12
< html >
< html >
< head >
     < title >Web page parsing</ title >
</ head >
< body >
     < div >
         < h1 >Web page parsing</ h1 >
         < p >This is an example Web page.</ p >
     </ div >
</ body >
</ html >

上面這段HTML會解析成這樣:

下面是另外一個有SVG標籤的狀況。

CSS解析

CSS的解析大概是下面這個樣子(下面主要說的是Gecko也就是Firefox的玩法),假設咱們有下面的HTML文檔:

1
2
3
4
5
6
7
8
9
< doc >
< title >A few quotes</ title >
< para >
   Franklin said that < quote >"A penny saved is a penny earned."</ quote >
</ para >
< para >
   FDR said < quote >"We have nothing to fear but < span >fear itself.</ span >"</ quote >
</ para >
</ doc >

因而DOM Tree是這個樣子:

而後咱們的CSS文檔是這樣的:

1
2
3
4
  /* rule 1 */ doc { display: block; text-indent: 1em; }
/* rule 2 */ title { display: block; font-size: 3em; }
/* rule 3 */ para { display: block; }
/* rule 4 */ [class="emph"] { font-style: italic; }

因而咱們的CSS Rule Tree會是這個樣子:

注意,圖中的第4條規則出現了兩次,一次是獨立的,一次是在規則3的子結點。因此,咱們能夠知道,創建CSS Rule Tree是須要比照着DOM Tree來的。CSS匹配DOM Tree主要是從右到左解析CSS的Selector,好多人覺得這個事會比較快,其實並不必定。關鍵還看咱們的CSS的Selector怎麼寫了。

注意:CSS匹配HTML元素是一個至關複雜和有性能問題的事情。因此,你就會在N多地方看到不少人都告訴你,DOM樹要小,CSS儘可能用id和class,千萬不要過渡層疊下去,……

經過這兩個樹,咱們能夠獲得一個叫Style Context Tree,也就是下面這樣(把CSS Rule結點Attach到DOM Tree上):

因此,Firefox基本上來講是經過CSS 解析 生成 CSS Rule Tree,而後,經過比對DOM生成Style Context Tree,而後Firefox經過把Style Context Tree和其Render Tree(Frame Tree)關聯上,就完成了。注意:Render Tree會把一些不可見的結點去除掉。而Firefox中所謂的Frame就是一個DOM結點,不要被其名字所迷惑了

注:Webkit不像Firefox要用兩個樹來幹這個,Webkit也有Style對象,它直接把這個Style對象存在了相應的DOM結點上了。

渲染

渲染的流程基本上以下(黃色的四個步驟):

  1. 計算CSS樣式
  2. 構建Render Tree
  3. Layout – 定位座標和大小,是否換行,各類position, overflow, z-index屬性 ……
  4. 正式開畫

注意:上圖流程中有不少鏈接線,這表示了Javascript動態修改了DOM屬性或是CSS屬會致使從新Layout,有些改變不會,就是那些指到天上的箭頭,好比,修改後的CSS rule沒有被匹配到,等。

這裏重要要說兩個概念,一個是Reflow,另外一個是Repaint。這兩個不是一回事。

  • Repaint——屏幕的一部分要重畫,好比某個CSS的背景色變了。可是元素的幾何尺寸沒有變。
  • Reflow——意味着元件的幾何尺寸變了,咱們須要從新驗證並計算Render Tree。是Render Tree的一部分或所有發生了變化。這就是Reflow,或是Layout。(HTML使用的是flow based layout,也就是流式佈局,因此,若是某元件的幾何尺寸發生了變化,須要從新佈局,也就叫reflow)reflow 會從<html>這個root frame開始遞歸往下,依次計算全部的結點幾何尺寸和位置,在reflow過程當中,可能會增長一些frame,好比一個文本字符串必需被包裝起來。

下面是一個打開Wikipedia時的Layout/reflow的視頻(注:HTML在初始化的時候也會作一次reflow,叫intial reflow),你能夠感覺一下:

 

Reflow的成本比Repaint的成本高得多的多。DOM Tree裏的每一個結點都會有reflow方法,一個結點的reflow頗有可能致使子結點,甚至父點以及同級結點的reflow。在一些高性能的電腦上也許還沒什麼,可是若是reflow發生在手機上,那麼這個過程是很是痛苦和耗電的

 

因此,下面這些動做有很大可能會是成本比較高的。

  • 當你增長、刪除、修改DOM結點時,會致使Reflow或Repaint
  • 當你移動DOM的位置,或是搞個動畫的時候。
  • 當你修改CSS樣式的時候。
  • 當你Resize窗口的時候(移動端沒有這個問題),或是滾動的時候。
  • 當你修改網頁的默認字體時。

注:display:none會觸發reflow,而visibility:hidden只會觸發repaint,由於沒有發現位置變化。

多說兩句關於滾屏的事,一般來講,若是在滾屏的時候,咱們的頁面上的全部的像素都會跟着滾動,那麼性能上沒什麼問題,由於咱們的顯卡對於這種把全屏像素往上往下移的算法是很快。可是若是你有一個fixed的背景圖,或是有些Element不跟着滾動,有些Elment是動畫,那麼這個滾動的動做對於瀏覽器來講會是至關至關痛苦的一個過程。你能夠看到不少這樣的網頁在滾動的時候性能有多差。由於滾屏也有可能會形成reflow。

基本上來講,reflow有以下的幾個緣由:

  • Initial。網頁初始化的時候。
  • Incremental。一些Javascript在操做DOM Tree時。
  • Resize。其些元件的尺寸變了。
  • StyleChange。若是CSS的屬性發生變化了。
  • Dirty。幾個Incremental的reflow發生在同一個frame的子樹上。

好了,咱們來看一個示例吧:

1
2
3
4
5
6
7
8
9
10
11
12
var bstyle = document.body.style; // cache
 
bstyle.padding = "20px" ; // reflow, repaint
bstyle.border = "10px solid red" ; //  再一次的 reflow 和 repaint
 
bstyle.color = "blue" ; // repaint
bstyle.backgroundColor = "#fad" ; // repaint
 
bstyle.fontSize = "2em" ; // reflow, repaint
 
// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode( 'dude!' ));

固然,咱們的瀏覽器是聰明的,它不會像上面那樣,你每改一次樣式,它就reflow或repaint一次。通常來講,瀏覽器會把這樣的操做積攢一批,而後作一次reflow,這又叫異步reflow或增量異步reflow。可是有些狀況瀏覽器是不會這麼作的,好比:resize窗口,改變了頁面默認的字體,等。對於這些操做,瀏覽器會立刻進行reflow。

可是有些時候,咱們的腳本會阻止瀏覽器這麼幹,好比:若是咱們請求下面的一些DOM值:

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. IE中的 getComputedStyle(), 或 currentStyle

由於,若是咱們的程序須要這些值,那麼瀏覽器須要返回最新的值,而這樣同樣會flush出去一些樣式的改變,從而形成頻繁的reflow/repaint。

減小reflow/repaint

下面是一些Best Practices:

1)不要一條一條地修改DOM的樣式。與其這樣,還不如預先定義好css的class,而後修改DOM的className。

1
2
3
4
5
6
7
8
9
10
11
// bad
var left = 10,
top = 10;
el.style.left = left + "px" ;
el.style.top  = top  + "px" ;
 
// Good
el.className += " theclassname" ;
 
// Good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;" ;

2)把DOM離線後修改。如:

  • 使用documentFragment 對象在內存裏操做DOM
  • 先把DOM給display:none(有一次reflow),而後你想怎麼改就怎麼改。好比修改100次,而後再把他顯示出來。
  • clone一個DOM結點到內存裏,而後想怎麼改就怎麼改,改完後,和在線的那個的交換一下。

3)不要把DOM結點的屬性值放在一個循環裏當成循環裏的變量。否則這會致使大量地讀寫這個結點的屬性。

4)儘量的修改層級比較低的DOM。固然,改變層級比較底的DOM有可能會形成大面積的reflow,可是也可能影響範圍很小。

5)爲動畫的HTML元件使用fixed或absoult的position,那麼修改他們的CSS是不會reflow的。

6)千萬不要使用table佈局。由於可能很小的一個小改動會形成整個table的從新佈局。

In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.

Fixed layout, CSS 2.1 Specification

This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.

Automatic layout, CSS 2.1 Specification

幾個工具和幾篇文章

有時候,你會也許會發如今IE下,你不知道你修改了什麼東西,結果CPU一會兒就上去了到100%,而後過了好幾秒鐘repaint/reflow才完成,這種事情以IE的年代時常常發生。因此,咱們須要一些工具幫咱們看看咱們的代碼裏有沒有什麼不合適的東西。

  • Chrome下,Google的SpeedTracer是個很是強悍的工做讓你看看你的瀏覽渲染的成本有多大。其實Safari和Chrome均可以使用開發者工具裏的一個Timeline的東東。
  • IE下你能夠用一個叫dynaTrace的IE擴展。

最後,別忘了下面這幾篇提升瀏覽器性能的文章:

參考

相關文章
相關標籤/搜索