瀏覽器的迴流與重繪 (Reflow & Repaint)

腰花發表於2018-03-02

寫在前面

在討論迴流與重繪之前,我們要知道:

  1. 瀏覽器使用流式佈局模型 (Flow Based Layout)。
  2. 瀏覽器會把HTML解析成DOM,把CSS解析成CSSOMDOMCSSOM合併就產生了Render Tree
  3. 有了RenderTree,我們就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。
  4. 由於瀏覽器使用流式佈局,對Render Tree的計算通常只需要遍歷一次就可以完成,但table及其內部元素除外,他們可能需要多次計算,通常要花3倍於同等元素的時間,這也是為什麼要避免使用table佈局的原因之一。

一句話:迴流必將引起重繪,重繪不一定會引起迴流。

迴流 (Reflow)

Render Tree中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文件的過程稱為迴流。

會導致迴流的操作:

  • 頁面首次渲染
  • 瀏覽器視窗大小發生改變
  • 元素尺寸或位置發生改變
  • 元素內容變化(文字數量或圖片大小等等)
  • 元素字型大小變化
  • 新增或者刪除可見DOM元素
  • 啟用CSS偽類(例如::hover
  • 查詢某些屬性或呼叫某些方法

一些常用且會導致迴流的屬性和方法:

  • clientWidthclientHeightclientTopclientLeft
  • offsetWidthoffsetHeightoffsetTopoffsetLeft
  • scrollWidthscrollHeightscrollTopscrollLeft
  • scrollIntoView()scrollIntoViewIfNeeded()
  • getComputedStyle()
  • getBoundingClientRect()
  • scrollTo()

重繪 (Repaint)

當頁面中元素樣式的改變並不影響它在文件流中的位置時(例如:colorbackground-colorvisibility等),瀏覽器會將新樣式賦予給元素並重新繪製它,這個過程稱為重繪。

效能影響

迴流比重繪的代價要更高。

有時即使僅僅迴流一個單一的元素,它的父元素以及任何跟隨它的元素也會產生迴流。

現代瀏覽器會對頻繁的迴流或重繪操作進行優化:

瀏覽器會維護一個佇列,把所有引起迴流和重繪的操作放入佇列中,如果佇列中的任務數量或者時間間隔達到一個閾值的,瀏覽器就會將佇列清空,進行一次批處理,這樣可以把多次迴流和重繪變成一次。

當你訪問以下屬性或方法時,瀏覽器會立刻清空佇列:

  • clientWidthclientHeightclientTopclientLeft

  • offsetWidthoffsetHeightoffsetTopoffsetLeft

  • scrollWidthscrollHeightscrollTopscrollLeft

  • widthheight

  • getComputedStyle()

  • getBoundingClientRect()

因為佇列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的資訊與佇列中操作引發的改變無關,瀏覽器也會強行清空佇列,確保你拿到的值是最精確的。

如何避免

CSS

  • 避免使用table佈局。
  • 儘可能在DOM樹的最末端改變class
  • 避免設定多層內聯樣式。
  • 將動畫效果應用到position屬性為absolutefixed的元素上。
  • 避免使用CSS表示式(例如:calc())。

JavaScript

  • 避免頻繁操作樣式,最好一次性重寫style屬性,或者將樣式列表定義為class並一次性更改class屬性。
  • 避免頻繁操作DOM,建立一個documentFragment,在它上面應用所有DOM操作,最後再把它新增到文件中。
  • 也可以先為元素設定display: none,操作結束後再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發迴流和重繪。
  • 避免頻繁讀取會引發迴流/重繪的屬性,如果確實需要多次使用,就用一個變數快取起來。
  • 對具有複雜動畫的元素使用絕對定位,使它脫離文件流,否則會引起父元素及後續元素頻繁迴流。

相關文章