回流與重繪

2021-10-09 05:21:31 字數 4095 閱讀 6253

回流:當我們對 dom 的修改引發了 dom 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然後再將計算的結果繪製出來。這個過程就是回流(也叫重排)。

重繪:當我們對 dom 的修改導致了樣式的變化、卻並未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪製新的樣式(跳過了上圖所示的回流環節)。這個過程叫做重繪。

由此我們可以看出,重繪不一定導致回流,回流一定會導致重繪。硬要比較的話,回流比重繪做的事情更多,帶來的開銷也更大。但這兩個都會耗費效能的,所以我們在開發中,要從**層面出發,盡可能把回流和重繪的次數最小化。

哪些實際操作會導致回流與重繪

觸發重繪的「導火索」比較好識別——只要是不觸發回流,但又觸發了樣式改變的 dom 操作,都會引起重繪,比如背景色、文本色、可見性(可見性這裡特指形如visibility: hidden這樣不改變元素位置和存在性的、單純針對可見性的操作,注意與display:none進行區分)等。為此,我們要著重理解一下那些可能觸發回流的操作。

回流的「導火索」

最「貴」的操作:改變 dom 元素的幾何屬性

「**適中」的操作:改變 dom 樹的結構

這裡主要指的是節點的增減、移動等操作。瀏覽器引擎布局的過程,順序上可以模擬於樹的前序遍歷——它是乙個從上到下、從左到右的過程。通常在這個過程中,當前元素不會再影響其前面已經遍歷過的元素。

最容易被忽略的操作:獲取一些特定屬性的值

當你要用到像這樣的屬性:offsettop、offsetleft、 offsetwidth、offsetheight、scrolltop、scrollleft、scrollwidth、scrollheight、clienttop、clientleft、clientwidth、clientheight 時,你就要注意了!

「像這樣」的屬性,到底是像什麼樣?——這些值有乙個共性,就是需要通過即時計算得到。因此瀏覽器為了獲取這些值,也會進行回流。

除此之外,當我們呼叫了 getcomputedstyle 方法,或者 ie 裡的 currentstyle 時,也會觸發回流。原理是一樣的,都為求乙個「即時性」和「準確性」。

如何規避回流與重繪

了解了回流與重繪的「導火索」,我們就要盡量規避它們。但很多時候,我們不得不使用它們。當避無可避時,我們就要學會更聰明地使用它們。

將「導火索」快取起來,避免頻繁改動

有時我們想要通過多次計算得到乙個元素的布局位置,我們可能會這樣做:

這樣做,每次迴圈都需要獲取多次「敏感屬性」,是比較糟糕的。我們可以將其以 js 變數的形式快取起來,待計算完畢再提交給瀏覽器發出重計算請求:

// 快取offsetleft與offsettop的值

const el = document.getelementbyid('el')

let offleft = el.offsetleft, offtop = el.offsettop

// 在js層面進行計算

for(let i=0;i<10;i++)

// 一次性將計算結果應用到dom上

el.style.left = offleft + "px"

el.style.top = offtop + "px"

避免逐條改變樣式,使用類名去合併樣式

比如我們可以把這段單純的**:

const container = document.getelementbyid('container')

container.style.width = '100px'

container.style.height = '200px'

container.style.border = '10px solid red'

container.style.color = 'red'

優化成乙個有 class 加持的樣子:

前者每次單獨操作,都去觸發一次渲染樹更改,從而導致相應的回流與重繪過程。

合併之後,等於我們將所有的更改一次性發出,用乙個 style 請求解決掉了。

將 dom 「離線」

我們上文所說的回流和重繪,都是在「該元素位於頁面上」的前提下會發生的。一旦我們給元素設定 display: none,將其從頁面上「拿掉」,那麼我們的後續操作,將無法觸發回流與重繪——這個將元素「拿掉」的操作,就叫做 dom 離線化。

仍以我們上文的**片段為例:

const container = document.getelementbyid('container')

container.style.width = '100px'

container.style.height = '200px'

container.style.border = '10px solid red'

container.style.color = 'red'

...(省略了許多類似的後續操作)

離線化後就是這樣:

let container = document.getelementbyid('container')

container.style.display = 'none'

container.style.width = '100px'

container.style.height = '200px'

container.style.border = '10px solid red'

container.style.color = 'red'

...(省略了許多類似的後續操作)

container.style.display = 'block'

有的同學會問,拿掉乙個元素再把它放回去,這不也會觸發一次昂貴的回流嗎?這話不假,但我們把它拿下來了,後續不管我操作這個元素多少次,每一步的操作成本都會非常低。當我們只需要進行很少的 dom 操作時,dom 離線化的優越性確實不太明顯。一旦操作頻繁起來,這「拿掉」和「放回」的開銷都將會是非常值得的。

flush 佇列:瀏覽器並沒有那麼簡單

q:下面幾行**。觸發了幾次回流與重繪呢?

let container = document.getelementbyid('container')

container.style.width = '100px'

container.style.height = '200px'

container.style.border = '10px solid red'

container.style.color = 'red'

a:「width、height、border是幾何屬性,各觸發一次回流;color只造成外觀的變化,會觸發一次重繪。」

現代瀏覽器是很聰明的。瀏覽器自己也清楚,如果每次 dom 操作都即時地反饋一次回流或重繪,那麼效能上來說是扛不住的。於是它自己快取了乙個 flush 佇列,把我們觸發的回流與重繪任務都塞進去,待到佇列裡的任務多起來、或者達到了一定的時間間隔,或者「不得已」的時候,再將這些任務一口氣出隊。

回流與重繪

首先是html渲染過程 解析html並構建dom樹和cssom樹,瀏覽器對html標記轉換成文件物件模型,css標記則轉換成css物件模型 cssom dom 樹包含了所有的 html 標籤,包括不展示的 head 節點和 display none 的節點,而 cssom 樹則會去掉瀏覽器不能識別的...

重繪與回流

很多面試都會問到的問題,那麼說起這兩個概念,首先先了解一下,瀏覽器對乙個頁面對渲染過程。1.使用者輸入url位址,瀏覽器根據網域名稱查詢ip位址 2.瀏覽器向伺服器傳送http請求 3.伺服器接受請求,根據請求返回相應的html 返回給瀏覽器 4.瀏覽器接受到伺服器的相應結果,對頁面做解析渲染 1 ...

重繪與回流

dom樹結構變化 新增或者刪除可見的dom元素 元素幾何屬性發生變化 頁面渲染初始化 獲取某些屬性 瀏覽器視窗發生變化,即 resize事件發生 啟用css偽類 hover 改變元素顏色 改變元素背景色 將改變樣式的操作集合在一次完成,直接改變classname或csstext 讓要操作的元素進行離...