寫在前面
在討論迴流與重繪之前,我們要知道:
- 瀏覽器使用流式佈局模型 (Flow Based Layout)。
- 瀏覽器會把
HTML
解析成DOM
,把CSS
解析成CSSOM
,DOM
和CSSOM
合併就產生了Render Tree
。 - 有了
RenderTree
,我們就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。 - 由於瀏覽器使用流式佈局,對
Render Tree
的計算通常只需要遍歷一次就可以完成,但table
及其內部元素除外,他們可能需要多次計算,通常要花3倍於同等元素的時間,這也是為什麼要避免使用table
佈局的原因之一。
一句話:迴流必將引起重繪,重繪不一定會引起迴流。
迴流 (Reflow)
當Render Tree
中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文件的過程稱為迴流。
會導致迴流的操作:
- 頁面首次渲染
- 瀏覽器視窗大小發生改變
- 元素尺寸或位置發生改變
- 元素內容變化(文字數量或圖片大小等等)
- 元素字型大小變化
- 新增或者刪除可見的
DOM
元素 - 啟用
CSS
偽類(例如::hover
) - 查詢某些屬性或呼叫某些方法
一些常用且會導致迴流的屬性和方法:
clientWidth
、clientHeight
、clientTop
、clientLeft
offsetWidth
、offsetHeight
、offsetTop
、offsetLeft
scrollWidth
、scrollHeight
、scrollTop
、scrollLeft
scrollIntoView()
、scrollIntoViewIfNeeded()
getComputedStyle()
getBoundingClientRect()
scrollTo()
重繪 (Repaint)
當頁面中元素樣式的改變並不影響它在文件流中的位置時(例如:color
、background-color
、visibility
等),瀏覽器會將新樣式賦予給元素並重新繪製它,這個過程稱為重繪。
效能影響
迴流比重繪的代價要更高。
有時即使僅僅迴流一個單一的元素,它的父元素以及任何跟隨它的元素也會產生迴流。
現代瀏覽器會對頻繁的迴流或重繪操作進行優化:
瀏覽器會維護一個佇列,把所有引起迴流和重繪的操作放入佇列中,如果佇列中的任務數量或者時間間隔達到一個閾值的,瀏覽器就會將佇列清空,進行一次批處理,這樣可以把多次迴流和重繪變成一次。
當你訪問以下屬性或方法時,瀏覽器會立刻清空佇列:
-
clientWidth
、clientHeight
、clientTop
、clientLeft
-
offsetWidth
、offsetHeight
、offsetTop
、offsetLeft
-
scrollWidth
、scrollHeight
、scrollTop
、scrollLeft
-
width
、height
-
getComputedStyle()
-
getBoundingClientRect()
因為佇列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的資訊與佇列中操作引發的改變無關,瀏覽器也會強行清空佇列,確保你拿到的值是最精確的。
如何避免
CSS
- 避免使用
table
佈局。 - 儘可能在
DOM
樹的最末端改變class
。 - 避免設定多層內聯樣式。
- 將動畫效果應用到
position
屬性為absolute
或fixed
的元素上。 - 避免使用
CSS
表示式(例如:calc()
)。
JavaScript
- 避免頻繁操作樣式,最好一次性重寫
style
屬性,或者將樣式列表定義為class
並一次性更改class
屬性。 - 避免頻繁操作
DOM
,建立一個documentFragment
,在它上面應用所有DOM操作
,最後再把它新增到文件中。 - 也可以先為元素設定
display: none
,操作結束後再把它顯示出來。因為在display
屬性為none
的元素上進行的DOM
操作不會引發迴流和重繪。 - 避免頻繁讀取會引發迴流/重繪的屬性,如果確實需要多次使用,就用一個變數快取起來。
- 對具有複雜動畫的元素使用絕對定位,使它脫離文件流,否則會引起父元素及後續元素頻繁迴流。