寫在前面
在討論迴流與重繪之前,我們要知道:
- 瀏覽器使用流式佈局模型 (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
操作不會引發迴流和重繪。 - 避免頻繁讀取會引發迴流/重繪的屬性,如果確實需要多次使用,就用一個變量緩存起來。
- 對具有複雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁迴流。