天天看點

浏覽器工作原理(七):布局(Layout)

當渲染對象被建立并添加到樹中,它們并沒有位置和大小,計算這些值的過程稱為layout或reflow。

  Html使用基于流的布局模型,意味着大部分時間,可以以單一的途徑進行幾何計算。流中靠後的元素并不會影響前面元素的幾何特性,是以布局可以在文檔中從右向左、自上而下的進行。也存在一些例外,比如html tables。

  坐标系統相對于根frame,使用top和left坐标。

  布局是一個遞歸的過程,由根渲染對象開始,它對應html文檔元素,布局繼續遞歸的通過一些或所有的frame層級,為每個需要幾何資訊的渲染對象進行計算。

  根渲染對象的位置是0,0,它的大小是viewport-浏覽器視窗的可見部分。

  所有的渲染對象都有一個layout或reflow方法,每個渲染對象調用需要布局的children的layout方法。

  Dirty bit系統

  為了不因為每個小變化都全部重新布局,浏覽器使用一個dirty bit系統,一個渲染對象發生了變化或是被添加了,就标記它及它的children為dirty——需要layout。存在兩個辨別——dirty及children are dirty,children are dirty說明即使這個渲染對象可能沒問題,但它至少有一個child需要layout。

  全局和增量layout

  當layout在整棵渲染樹觸發時,稱為全局layout,這可能在下面這些情況下發生:

  1. 一個全局的樣式改變影響所有的渲染對象,比如字号的改變。

  2. 視窗resize。

  layout也可以是增量的,這樣隻有标志為dirty的渲染對象會重新布局(也将導緻一些額外的布局)。增量layout會在渲染對象dirty時異步觸發,例如,當網絡接收到新的内容并添加到Dom樹後,新的渲染對象會添加到渲染樹中。

浏覽器工作原理(七):布局(Layout)

圖20:增量layout

異步和同步layout

  增量layout的過程是異步的,Firefox為增量layout生成了reflow隊列,以及一個排程執行這些批處理指令。WebKit也有一個計時器用來執行增量layout-周遊樹,為dirty狀态的渲染對象重新布局。

  另外,當腳本請求樣式資訊時,例如“offsetHeight”,會同步的觸發增量布局。

  全局的layout一般都是同步觸發。

  有些時候,layout會被作為一個初始layout之後的回調,比如滑動條的滑動。

  優化

  當一個layout因為resize或是渲染位置改變(并不是大小改變)而觸發時,渲染對象的大小将會從緩存中讀取,而不會重新計算。

  一般情況下,如果隻有子樹發生改變,則layout并不從根開始。這種情況發生在,變化發生在元素自身并且不影響它周圍元素,例如,将文本插入文本域(否則,每次擊鍵都将觸發從根開始的重排)。

layout過程

  layout一般有下面這幾個部分:

  1. parent渲染對象決定它的寬度

  2. parent渲染對象讀取chilidren,并:

    a. 放置child渲染對象(設定它的x和y)

    b. 在需要時(它們目前為dirty或是處于全局layout或者其他原因)調用child渲染對象的layout,這将計算child的高度

    c. parent渲染對象使用child渲染對象的累積高度,以及margin和padding的高度來設定自己的高度-這将被parent渲染對象的parent使用

    d. 将dirty辨別設定為false

  Firefox使用一個“state”對象(nsHTMLReflowState)做為參數去布局(firefox稱為reflow),state包含parent的寬度及其他内容。

  Firefox布局的輸出是一個“metrics”對象(nsHTMLReflowMetrics)。它包括渲染對象計算出的高度。

寬度計算

  渲染對象的寬度使用容器的寬度、渲染對象樣式中的寬度及margin、border進行計算。例如,下面這個div的寬度:

  <div />

  webkit中寬度的計算過程是(RenderBox類的calcWidth方法):

  • 容器的寬度是容器的可用寬度和0中的最大值,這裡的可用寬度為:contentWidth=clientWidth()-paddingLeft()-paddingRight(),clientWidth和clientHeight代表一個對象内部的不包括border和滑動條的大小
  • 元素的寬度指樣式屬性width的值,它可以通過計算容器的百分比得到一個絕對值
  • 加上水準方向上的border和padding

  到這裡是最佳寬度的計算過程,現在計算寬度的最大值和最小值,如果最佳寬度大于最大寬度則使用最大寬度,如果小于最小寬度則使用最小寬度。最後緩存這個值,當需要layout但寬度未改變時使用。

Line breaking

當一個渲染對象在布局過程中需要折行時,則暫停并告訴它的parent它需要折行,parent将建立額外的渲染對象并調用它們的layout。

繼續閱讀