1. 程式人生 > >瀏覽器是如何工作的——渲染部分

瀏覽器是如何工作的——渲染部分

瀏覽器的基礎架構

  1. 介面元素(或介面控制元件) – 包括位址列,前進後退,書籤選單等視窗上除了網頁顯示區域以外的部分。
  2. 瀏覽器引擎 – 查詢與操作渲染引擎的介面。
  3. 渲染引擎 – 負責顯示請求的內容。比如請求到HTML, 它會負責解析HTML 與 CSS 並將結果顯示到視窗中。
  4. 網路 – 用於網路請求, 如HTTP請求。它包括平臺無關的介面和各平臺獨立的實現。
  5. UI後端 – 繪製基礎元件,如組合框與視窗。它提供平臺無關的介面,內部使用作業系統的相應實現。
  6. JavaScript直譯器。用於解析執行JavaScript程式碼。
  7. 資料儲存。這是一個持久層。瀏覽器需要把所有資料存到硬碟上,如cookies。新的HTML規範 (HTML5) 規定了一個完整(雖然輕量級)的瀏覽器中的資料庫:’web database’。

這裡寫圖片描述
 需要注意的是,與其它瀏覽器不同,chrome使用多個渲染引擎例項,每個Tab頁一個,即每個Tab都是一個獨立程序。

渲染過程

使用者請求的HTML文字(text/html)通過瀏覽器的網路層到達渲染引擎後,渲染工作開始。每次渲染文件通常不會超過8K的資料塊,其中基礎的渲染流程圖如下: 這裡寫圖片描述
 

詳細流程圖

 這裡寫圖片描述

渲染引擎首先解析HTML文件,轉換為一棵DOM樹,此為第一步。接下來不管是內聯式,外聯式還是嵌入式引入的CSS樣式也會被解析,同時生成另外一棵用於渲染DOM樹的樹-渲染樹(render tree) ,渲染樹包含帶有顏色,尺寸等顯示屬性的矩形,這些矩形的順序與顯示順序基本一致。然後就是對渲染樹的每個節點進行佈局處理,確定其在螢幕上的顯示位置。最後就是遍歷渲染樹並用上一章提到的UI後端層將每一個節點繪製出來。

  以上步驟是一個漸進的過程,為了提高使用者體驗,渲染引擎試圖儘可能快的把結果顯示給終端使用者。它不會等到所有HTML都被解析完才建立並佈局渲染樹。它會在從網路層獲取文件內容的同時把已經接收到的區域性內容先展示出來。

DOM樹

DOM全稱為Document Object Model,即我們所說的文件物件模型。我們可以把它看做是HTML元素對外的介面,有了這些介面javascript開發人員才能夠實現複雜的頁面功能。DOM樹的根節點是Document物件。
瀏覽器利用HTML解析器,分析接收到的HTML文字構建出dom object新增到dom樹相應位置上。解析的時候會利用DTD(Document Type Definition)規範描述。所有HTML引用的外部資源預設都是非同步載入的,即解析與下載資源同時進行,但由於指令碼資源(.js)在解析過程中就有可能執行,如document.write之類的,所以預設指令碼資源載入的時候是同步進行的,除非添加了defer或者asyn之類的屬性。CSS由於不會更改DOM物件的結構,其資源的載入是非同步的。這樣就又會產生一個問題,譬如<link href=a.css> 然後<script src=a.js>,a.js可能用到了a.css裡頭定義的class,但是由於a.css載入是非同步的,a.js卻是同步執行的,可能就會報錯,遇到這種情況,瀏覽器會進行特殊處理,當載入指令碼的時候如果當前還在載入樣式資源,則載入指令碼的操作會被阻塞知道樣式檔案載入完畢。
  當以上解析過程完畢後,瀏覽器繼續進行標記為deferred模式的腳步解析,然後就是整個解析過程的實際結束。文件狀態設定為complete,同時觸發load事件。

Render樹

瀏覽器在構造DOM樹的同時也在構造著另一棵樹-Render Tree,與DOM樹相對應暫且叫它Render樹吧,我們知道DOM樹為javascript提供了一些列的訪問介面(DOM API),但這棵樹是不對外的。它的主要作用就是把HTML按照一定的佈局與樣式顯示出來,用到了CSS的相關知識。從MVC的角度來說,可以將render樹看成是V,dom樹看成是M,C則是具體的排程者,比HTMLDocumentParser等。

  Render樹的每一個節點我們叫它renderer,其由下列型別的基礎類構造。

class RenderObject{
    virtual void layout();
    virtual void paint(PaintInfo);
    virtual void rect repaintRect();
    Node* node;  //the DOM node
    RenderStyle*  style;  // the computed style
    RenderLayer* containgLayer; //the containing z-index layer

}
DOM樹與Render樹
可以這麼說,沒有DOM樹就沒有Render樹,但是它們之間可不是簡單的一對一的關係。我們已經知道了render樹是用於顯示的,那不可見的元素當然不會在這棵樹中出現了,譬如<head>,您還能想到哪些呢?除此之外,diplay等於none的也不會被顯示在這棵樹裡頭,但是visibility等於hidden的元素是會顯示在這棵樹裡頭的,可以自己想一下為什麼。說了這麼多render樹,我們還沒見一下它的真容呢,它到底會是個什麼模樣呢?我們看一下圖。
這裡寫圖片描述
 DOM物件型別很豐富,什麼head,title,div,而Render樹相對來說就比較單一了,畢竟它的職責就是為了以後的顯示渲染用嘛。從上圖我們還可以看出,有些DOM元素沒有對應的renderer,而有些DOM元素卻對應了好幾個renderer,對應多個renderer的情況是普遍存在的,就是為了解決一個renderer描述不清楚如何顯示出來的問題,譬如select元素,我們就需要三個renderer,one for the display area, one for the drop down list box and one for the button。

  上圖中還有一種關係,即renderer與dom元素的位置也可能是不一樣的。說的就是那些添加了float:—或者position:absolute的元素,因為它們脫離了正常的文件流順序,構造Render樹的時候會針對它們實際的位置進行構造。

佈局與顯示

上面確定了renderer的樣式規則後,然後就是重要的顯示因素佈局了。當renderer構造出來並新增到render樹上之後,它並沒有位置跟大小資訊,為它確定這些資訊的過程,我們就稱之為佈局。
瀏覽器進行頁面佈局基本過程是以瀏覽器可見區域為畫布,左上角為(0,0)基礎座標,從左到右,從上到下從DOM的根節點開始畫,首先確定顯示元素的大小跟位置,此過程是通過瀏覽器計算出來的,使用者CSS中定義的量未必就是瀏覽器實際採用的量,靈活的佈局方式卻是靠經驗以及設計領域相關經驗等得來的。如果顯示元素有子元素得先去確定子元素的顯示資訊,如果頁面佈局完後有改動能不整體重新佈局就不重新佈局,當然由於指令碼或者使用者操作也有可能隨時會被重新佈局,那就回到了本段路起點。

高度和寬度

顯示元素的高度(height),寬度(width)的確定。元素的高度由子元素遍歷計算得來,而寬度由本身確定。

最終所有顯示元素被構建成一個盒子類似的東西構成了整個佈局效果,盒子模型效果圖如下:
這裡寫圖片描述

BOX TYPE

每個顯示元素上還有一個重要屬性就是display及z-index,display不同的型別使用不同的渲染方式,即使沒有手動去指定它本身也會存在一個預設值,具體預設值情況請參見 http://www.w3.org/TR/CSS2/sample.html.
  block型別的盒子具有自己的一塊區域,裡頭養了很多inline box。各個block box按上下順序部件,各個inline box按從左到右順序佈局,如果寬度不夠會自動換行。
這裡寫圖片描述
  

定位方案,座標值(left,top)

 Normal--定位基於基本佈局過程,從左到右,從上到下遞增實現。

 Float--脫離正常文件流,居最左或居最右。

 Absolute--其位置資訊由使用者指定。

有了size(width,height),position(left,top),box type(display:none,inline,block..e.t.c)一個顯示元素基本就最終確定了。

佈局完成後,瀏覽器將結果渲染出來,最終我們看到了眼前的頁面。

這裡寫圖片描述
實際上本文描述的是右半邊的操作,

過程小結

  1. 瀏覽器的網路層接收HTML文件
  2. 解析HTML文件,構建DOM object新增到DOM樹上
  3. 解析CSS,生成渲染樹(含有長寬顏色等資訊的矩形,順序與實際顯示順序基本一致)
  4. 渲染樹佈局(對渲染樹的每個節點計算位置和大小)
  5. 遍歷渲染樹用UI後端層把每個節點繪製出來

以上步驟是一個漸進的過程,為了提高使用者體驗,渲染引擎試圖儘可能快的把結果顯示給終端使用者。它不會等到所有HTML都被解析完才建立並佈局渲染樹。它會在從網路層獲取文件內容的同時把已經接收到的區域性內容先展示出來