迴流、重繪及其優化

迴流、重繪及其優化

渲染過程

渲染引擎經過經過網絡請求接收渲染內容javascript

  1. 解析HTML抽象DOM tree
  2. 抽象出Render tree
  3. 佈局(layout)render tree
  4. 繪畫render tree

抽象DOM tree

渲染引擎的第一步是解析html文檔並將解析的元素轉換爲dom樹中的實際dom節點。
image

抽象CSSOM tree

當瀏覽器解析dom的時候,遇到link標籤,引用外部的css樣式表,引擎會將css抽象成cssom
image

構建渲染樹

HTML中的可視指令與來自cssom樹的樣式數據結合使用來建立渲染樹。
image
爲了構建渲染樹,瀏覽器大體以下:
  • 從dom樹的根開始,遍歷每一個可見節點。某些節點不可見(例如,腳本標記,元標記等),而且因爲它們未反映在呈現的輸出中而被省略。display:none 也會使節點省略
  • 對於每一個可見節點,瀏覽器找到適當的匹配cssom規則並應用它們
  • 它會發布帶有內容和計算樣式的可見節點
  • 每一個渲染器表明一個矩形區域,一般對應於一個節點的CSS框。

它包括幾何信息,如寬度,高度和位置css

渲染樹的佈局

當渲染器被建立並添加到樹中時,它沒有位置和大小。計算這些值稱爲佈局。

html使用基於流的佈局模型,這意味着大多數時候它能夠一次性計算幾何。座標系相對於根渲染器。使用頂部和左側座標。html

佈局是一個遞歸過程,從根元素開始,也就是html,每一個渲染器都會去計算他本身的位置和大小html5

繪製渲染樹

在這個階段,遍歷渲染器樹,調用渲染器的paint()方法在屏幕上顯示內容。

渲染分爲全局渲染和增量渲染java

處理腳本和樣式表的順序

當解析器到達script標記時,腳本將被當即解析並執行。
文檔的解析將暫停,直到腳本執行完畢。
這意味着該過程是同步的

這也是爲何把script標籤放在body結束以前web

html5添加了一個選項,將腳本標記爲異步,以便它能夠被其餘線程解析和執行。json

迴流和重繪(reflow和repaint)

  • 迴流: 意味着元素的內容、結構、位置或尺寸發生了變化,須要從新計算樣式和渲染樹;
  • 重繪:意味着元素髮生的改變隻影響了節點的一些樣式(背景色,邊框顏色,文字顏色等),只須要應用新樣式繪製這個元素就能夠了;

什麼時候觸發迴流和重繪

  • repaint重繪:瀏覽器

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

    1. 頁面第一次渲染(初始化)
    2. DOM樹變化(如:增刪節點)
    3. Render樹變化(如:padding改變)
    4. 瀏覽器窗口resize
    5. 當你查詢佈局信息,包括offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、調用了getComputedStyle()或者IE的currentStyle時,瀏覽器爲了返回最新值,會觸發迴流。

優化渲染性能,減小回流和重繪

減小reflow和repaint網絡

  • 儘可能避免改變佈局屬性。如width, height, left, top。
  • 除了transforms 或者 opacity屬性都會引發重繪,作動畫的時候要注意,儘可能使用這兩個屬性;
  • 使用Flexbox。
  • 避免屢次讀取部分佈局屬性(同上)
  • 將複雜的節點元素脫離文檔流,下降回流成本

javascript

  1. 避免使用setTimeout setInterval 來更新視圖,這會在render以後提交修改需求
  2. 在micro-tasks中修改dom。這會在render以前提交修改需求
  3. 把script標籤放在body結束以前,或者使用異步script(defer, async)
  4. 把計算量大的js放在workers執行,例如解析一個大的json文件

CSS

  • 減小選擇器的複雜性。
  • 避免逐個修改節點樣式,儘可能一次性修改,減小style修改所影響元素的數量,使用cssText來替代要屢次修改的style屬性
// 設置單個屬性
elt.style.color = "blue"; 

// 在單個語句中設置多個樣式
elt.style.cssText = "color: blue; border: 1px solid black"; 

// 在單個語句中設置多個樣式
elt.setAttribute("style", "color:red; border: 1px solid blue;");
  • 經過改變類名來修改樣式

DOM

  1. 使元素脫離文檔流
  2. 對其應用多重修改
  3. 把元素帶回文檔中
這個過程會觸發兩次迴流,第一步和第三步。把會觸發屢次迴流的步驟放在第二步

三種基本方法:

  • display:none,而後修改樣式,而後在恢復
  • 使用文檔片斷(document fragment)在當前dom樹以外構建一個子樹,再把他拷貝迴文檔。
var fragment = document.createDocumentFragment()
... 在這裏進行dom操做,能夠減小回流和重繪的次數
document.getElementById('#app').appendChild(fragment)
  • 將原始元素拷貝到一個脫離文檔的節點中,修改副本,完成後替換原始元素
var old = document.getElementById('#app')
var clone = old.cloneNode(true)
... 在這裏進行dom操做,能夠減小回流和重繪的次數
old.parentNode.replaceChild(clone, old)

緩存佈局信息

前面提到在查詢佈局信息(offsetLeft...)的時候也會引發迴流,咱們在使用的時候能夠把佈局信息緩存起來,減小回流次數

這裏貼上<<高性能javascript>>中的例子:把myElement元素沿對角線移動,每次移動一個像素,從100100的位置開始,到500500的位置結束。在timeout循環體中你可使用下面的方法

// 低效的
myElement.style.left = 1 + myElement.offsetLeft + 'px'
myElement.style.top = 1 + myElement.offsetTop + 'px'
if (myElement.offsetTop >= 500) {
  stopAnimation();
}
// 優化
// 在循環外層獲取初始值
var current = myElement.offsetLeft
.
.
.
// 直接使用current變量,再也不查詢偏移量
current++
myElement.style.left = current + 'px'
myElement.style.top = current + 'px'
if (current >= 500) {
  stopAnimation();
}

使元素進行動畫效果的時候脫離文檔流
在元素髮生動畫效果的時候,會引發底部元素的迴流,這個影響可能很大,也可能很小,取決於元素在文檔流的位置

  1. 動畫元素使用絕對定位,使其脫離文檔流
  2. 這裏再進行旋轉,跳躍,都不會影響到整個頁面的迴流
  3. 在動畫結束時恢復定位,從而只會下移一次文檔的其餘元素。

參考DOM操做成本到底高在哪兒
參考高性能javascript
參考How JavaScript works: the rendering engine and tips to optimize its performance

相關文章
相關標籤/搜索