瀏覽器渲染機制

以前大概知道個流程,如今梳理下 印象深入。javascript

要了解瀏覽器渲染頁面的過程,首先得知道一個名詞——關鍵渲染路徑。關鍵渲染路徑是指瀏覽器從最初接收請求來的HTML、CSS、javascript等資源,而後解析、構建樹、渲染布局、繪製,最後呈現給客戶能看到的界面這整個過程。
用戶看到頁面實際上能夠分爲兩個階段:頁面內容加載完成和頁面資源加載完成,分別對應於DOMContentLoadedLoadcss

  • DOMContentLoaded事件觸發時,僅當DOM加載完成,不包括樣式表,圖片等
  • load事件觸發時,頁面上全部的DOM,樣式表,腳本,圖片都已加載完成,  load事件時間上比$(document).ready()還後面

瀏覽器渲染的過程主要包括如下五步:html

  1. 瀏覽器將獲取的HTML文檔解析成DOM樹。
  2. 處理CSS標記,構成層疊樣式表模型CSSOM(CSS Object Model)。
  3. 將DOM和CSSOM合併爲渲染樹(rendering tree)將會被建立,表明一系列將被渲染的對象。
  4. 渲染樹的每一個元素包含的內容都是計算過的,它被稱之爲佈局layout。瀏覽器使用一種流式處理的方法,只須要一次繪製操做就能夠佈局全部的元素。
  5. 將渲染樹的各個節點繪製到屏幕上,這一步被稱爲繪製painting

須要注意的是,以上五個步驟並不必定一次性順序完成,好比DOM或CSSOM被修改時,亦或是哪一個過程會重複執行,這樣才能計算出哪些像素須要在屏幕上進行從新渲染。而在實際狀況中,JavaScript和CSS的某些操做每每會屢次修改DOM或者CSSOM。java

 
webkit渲染引擎流程
 

構建DOM樹

當瀏覽器接收到服務器響應來的HTML文檔後,會遍歷文檔節點,生成DOM樹。
須要注意如下幾點:node

  • DOM樹在構建的過程當中可能會被CSS和JS的加載而執行阻塞
  • display:none的元素也會在DOM樹中
  • 註釋也會在DOM樹中
  • script標籤會在DOM樹中

不管是DOM仍是CSSOM,都是要通過Bytes→characters→tokens→nodes→objectmodel這個過程。linux

 
 

當前節點的全部子節點都構建好後纔會去構建當前節點的下一個兄弟節點。web

構建CSSOM規則樹

瀏覽器解析CSS文件並生成CSSOM,每一個CSS文件都被分析成一個StyleSheet對象,每一個對象都包含CSS規則。CSS規則對象包含對應於CSS語法的選擇器和聲明對象以及其餘對象。
在這個過程須要注意的是:後端

  • CSS解析能夠與DOM解析同時進行。
  • CSS解析與script的執行互斥 。
  • 在Webkit內核中進行了script執行優化,只有在JS訪問CSS時纔會發生互斥。

構建渲染樹(Render Tree)

經過DOM樹和CSS規則樹,瀏覽器就能夠經過它兩構建渲染樹了。瀏覽器會先從DOM樹的根節點開始遍歷每一個可見節點,而後對每一個可見節點找到適配的CSS樣式規則並應用。
有如下幾點須要注意:瀏覽器

  • Render Tree和DOM Tree不徹底對應
  • display: none的元素不在Render Tree中
  • visibility: hidden的元素在Render Tree中
 
 

渲染樹生成後,仍是沒有辦法渲染到屏幕上,渲染到屏幕須要獲得各個節點的位置信息,這就須要佈局(Layout)的處理了。緩存

渲染樹佈局(layout of the render tree)

佈局階段會從渲染樹的根節點開始遍歷,因爲渲染樹的每一個節點都是一個Render Object對象,包含寬高,位置,背景色等樣式信息。因此瀏覽器就能夠經過這些樣式信息來肯定每一個節點對象在頁面上的確切大小和位置,佈局階段的輸出就是咱們常說的盒子模型,它會精確地捕獲每一個元素在屏幕內的確切位置與大小。須要注意的是:

  • float元素,absoulte元素,fixed元素會發生位置偏移。
  • 咱們常說的脫離文檔流,其實就是脫離Render Tree。

渲染樹繪製(Painting the render tree)

在繪製階段,瀏覽器會遍歷渲染樹,調用渲染器的paint()方法在屏幕上顯示其內容。渲染樹的繪製工做是由瀏覽器的UI後端組件完成的。

瀏覽器渲染網頁的那些事兒

瀏覽器主要組件結構

 
瀏覽器主要組件

渲染引擎主要有兩個:webkit和Gecko
Firefox使用Geoko,Mozilla自主研發的渲染引擎。Safari和Chrome都使用webkit。Webkit是一款開源渲染引擎,它原本是爲linux平臺研發的,後來由Apple移植到Mac及Windows上。
雖然主流瀏覽器渲染過程叫法有區別,可是主要流程仍是相同的。

渲染阻塞

JS能夠操做DOM來修改DOM結構,能夠操做CSSOM來修改節點樣式,這就致使了瀏覽器在遇到<script>標籤時,DOM構建將暫停,直至腳本完成執行,而後繼續構建DOM。若是腳本是外部的,會等待腳本下載完畢,再繼續解析文檔。如今能夠在script標籤上增長屬性defer或者async。腳本解析會將腳本中改變DOM和CSS的地方分別解析出來,追加到DOM樹和CSSOM規則樹上。

每次去執行JavaScript腳本都會嚴重地阻塞DOM樹的構建,若是JavaScript腳本還操做了CSSOM,而正好這個CSSOM尚未下載和構建,瀏覽器甚至會延遲腳本執行和構建DOM,直至完成其CSSOM的下載和構建。因此,script標籤的位置很重要。

JS阻塞了構建DOM樹,也阻塞了其後的構建CSSOM規則樹,整個解析進程必須等待JS的執行完成纔可以繼續,這就是所謂的JS阻塞頁面。

因爲CSSOM負責存儲渲染信息,瀏覽器就必須保證在合成渲染樹以前,CSSOM是完備的,這種完備是指全部的CSS(內聯、內部和外部)都已經下載完,並解析完,只有CSSOM和DOM的解析徹底結束,瀏覽器纔會進入下一步的渲染,這就是CSS阻塞渲染。

CSS阻塞渲染意味着,在CSSOM完備前,頁面將一直處理白屏狀態,這就是爲何樣式放在head中,僅僅是爲了更快的解析CSS,保證更快的首次渲染。

須要注意的是,即使你沒有給頁面任何的樣式聲明,CSSOM依然會生成,默認生成的CSSOM自帶瀏覽器默認樣式。

當解析HTML的時候,會把新來的元素插入DOM樹裏面,同時去查找CSS,而後把對應的樣式規則應用到元素上,查找樣式表是按照從右到左的順序去匹配的。

例如:div p {font-size: 16px},會先尋找全部p標籤並判斷它的父標籤是否爲div以後纔會決定要不要採用這個樣式進行渲染)。
因此,咱們平時寫CSS時,儘可能用idclass,千萬不要過渡層疊。

迴流和重繪(reflow和repaint)

咱們都知道HTML默認是流式佈局的,但CSS和JS會打破這種佈局,改變DOM的外觀樣式以及大小和位置。所以咱們就須要知道兩個概念:replaintreflow

reflow(迴流)

當瀏覽器發現佈局發生了變化,這個時候就須要倒回去從新渲染,你們稱這個回退的過程叫reflowreflow會從html這個root frame開始遞歸往下,依次計算全部的結點幾何尺寸和位置,以確認是渲染樹的一部分發生變化仍是整個渲染樹。reflow幾乎是沒法避免的,由於只要用戶進行交互操做,就勢必會發生頁面的一部分的從新渲染,且一般咱們也沒法預估瀏覽器到底會reflow哪一部分的代碼,由於他們會相互影響。

repaint(重繪)

repaint則是當咱們改變某個元素的背景色、文字顏色、邊框顏色等等不影響它周圍或內部佈局的屬性時,屏幕的一部分要重畫,可是元素的幾何尺寸和位置沒有發生改變。

須要注意的是,display:none會觸發reflow,而visibility: hidden屬性則並不算是不可見屬性,它的語義是隱藏元素,但元素仍然佔據着佈局空間,它會被渲染成一個空框。因此visibility:hidden只會觸發repaint,由於沒有發生位置變化。

另外有些狀況下,好比修改了元素的樣式,瀏覽器並不會馬上reflowrepaint一次,而是會把這樣的操做積攢一批,而後作一次reflow,這又叫異步reflow或增量異步reflow。可是在有些狀況下,好比resize窗口,改變了頁面默認的字體等。對於這些操做,瀏覽器會立刻進行reflow

引發reflow

現代瀏覽器會對迴流作優化,它會等到足夠數量的變化發生,再作一次批處理迴流。

  • 頁面第一次渲染(初始化)
  • DOM樹變化(如:增刪節點)
  • Render樹變化(如:padding改變)
  • 瀏覽器窗口resize
  • 獲取元素的某些屬性

瀏覽器爲了得到正確的值也會提早觸發迴流,這樣就使得瀏覽器的優化失效了,這些屬性包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、調用了getComputedStyle()

引發repaint

reflow迴流一定引發repaint重繪,重繪能夠單獨觸發。
背景色、顏色、字體改變(注意:字體大小發生變化時,會觸發迴流)

減小reflow、repaint觸發次數

  • transform作形變和位移能夠減小reflow
  • 避免逐個修改節點樣式,儘可能一次性修改
  • 使用DocumentFragment將須要屢次修改的DOM元素緩存,最後一次性append到真實DOM中渲染
  • 能夠將須要屢次修改的DOM元素設置display:none,操做完再顯示。(由於隱藏元素不在render樹內,所以修改隱藏元素不會觸發迴流重繪)
  • 避免屢次讀取某些屬性
  • 經過絕對位移將複雜的節點元素脫離文檔流,造成新的Render Layer,下降回流成本

幾條關於優化渲染效率的建議

結合上文有如下幾點能夠優化渲染效率。

  • 合法地去書寫HTML和CSS ,且不要忘了文檔編碼類型。
  • 樣式文件應當在head標籤中,而腳本文件在body結束前,這樣能夠防止阻塞的方式。
  • 簡化並優化CSS選擇器,儘可能將嵌套層減小到最小。
  • DOM 的多個讀操做(或多個寫操做),應該放在一塊兒。不要兩個讀操做之間,加入一個寫操做。
  • 若是某個樣式是經過重排獲得的,那麼最好緩存結果。避免下一次用到的時候,瀏覽器又要重排。
  • 不要一條條地改變樣式,而要經過改變class,或者csstext屬性,一次性地改變樣式。
  • 儘可能用transform來作形變和位移
  • 儘可能使用離線DOM,而不是真實的網面DOM,來改變元素樣式。好比,操做Document Fragment對象,完成後再把這個對象加入DOM。再好比,使用cloneNode()方法,在克隆的節點上進行操做,而後再用克隆的節點替換原始節點。
  • 先將元素設爲display: none(須要1次重排和重繪),而後對這個節點進行100次操做,最後再恢復顯示(須要1次重排和重繪)。這樣一來,你就用兩次從新渲染,取代了可能高達100次的從新渲染。
  • position屬性爲absolutefixed的元素,重排的開銷會比較小,由於不用考慮它對其餘元素的影響。
  • 只在必要的時候,纔將元素的display屬性爲可見,由於不可見的元素不影響重排和重繪。另外,visibility : hidden的元素只對重繪有影響,不影響重排。
    使用window.requestAnimationFrame()window.requestIdleCallback()這兩個方法調節從新渲染。
 
 
相關文章
相關標籤/搜索