前言
回流與重繪對於前端來說可以說是非常重要的知識點了,我們不僅需要知道什么是回流與重繪,還需要知道如何進行優化。一個頁面從加載到完成,首先是構建DOM樹,然后根據DOM節點的幾何屬性形成render樹(渲染樹),當渲染樹構建完成,頁面就根據DOM樹開始布局了,渲染樹也根據設置的樣式對應的渲染這些節點。在這個過程中,回流與DOM樹,渲染樹有關,重繪與渲染樹有關。
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新的文章~
頁面渲染過程

- 解析HTML構建DOM Tree
- 解析CSS構建CSSOM Tree
- 構建渲染樹(Render Tree),渲染樹🌲只包含渲染網頁所需的節點
為構建渲染樹,瀏覽器大體上完成了下列工作:
- 從 DOM 樹的根節點開始遍歷每個可見節點。
- 某些節點不可見(例如腳本標記、元標記等),因為它們不會體現在渲染輸出中,所以會被忽略。
- 某些節點通過 CSS 隱藏(例如display: none),因此在渲染樹中也會被忽略。
- 對於每個可見節點,為其找到適配的 CSSOM 規則並應用它們。
- 發射可見節點,連同其內容和計算的樣式。
Note: 請注意 visibility: hidden 與 display: none 是不一樣的。前者隱藏元素,但元素仍占據着布局空間(即將其渲染成一個空框),而后者 (display: none) 將元素從渲染樹中完全移除,元素既不可見,也不是布局的組成部分。
最終輸出的渲染同時包含了屏幕上的所有可見內容及其樣式信息。有了渲染樹,我們就可以進入“布局”階段。
- 布局計算每個DOM對象的精確位置和大小
- 渲染(繪制,合成),使用最終渲染樹將像素渲染到屏幕上
有關頁面渲染的過程可以看我之前的文章:超詳細講解頁面加載過程,這里我們把重點放在重繪與回流上。
什么是回流(Reflow)與重繪(Repaint)?
回流(Reflow)
當渲染樹render tree中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流(reflow)。每個頁面至少需要一次回流,就是在頁面第一次加載的時候,這時候是一定會發生回流的,因為要構建render tree。在回流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並重新構造這部分渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程稱為重繪。
簡單來說,回流就是計算元素在設備內的確切位置和大小並且重新繪制
回流的代價要遠大於重繪。並且回流必然會造成重繪,但重繪不一定會造成回流。
重繪(Repaint)
當渲染樹render tree中的一些元素需要更新樣式,但這些樣式屬性只是改變元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪(repaint)。
簡單來說,重繪就是將渲染樹節點轉換為屏幕上的實際像素,不涉及重新布局階段的位置與大小計算
為什么不建議頻繁操作DOM?
我們都知道操作DOM其實是非常耗性能的,所以我們不僅要避免去操作DOM,還要減少訪問DOM的次數。
因為在瀏覽器中,DOM和JS的實現,並不是在同一個引擎中完成的。DOM是屬於渲染引擎中的東⻄,⽽JS⼜是JS引擎中的東⻄。當我們通過JS操作DOM的時候,就涉及到了兩個線程之間的通信,那么勢必會帶來⼀些性能上的損耗。操作DOM次數⼀多,也就等同於⼀直在進⾏線程之間的通信,並且操作DOM可能還會帶來重繪回流的情況,所以也就導致了性能上的問題。
把DOM和JavaScript各自想象成一個島嶼,它們之間用收費橋梁連接。
--《高性能JavaScript》
何時會發生回流(Reflow)與重繪(Repaint)?
會導致回流的操作:
- 頁面首次渲染(無法避免且開銷最大的一次)
- 瀏覽器窗口大小發生改變(resize事件)
- 元素尺寸或位置發生改變(邊距、寬高、邊框等)
- 元素內容變化(文字數量或圖片大小等等)
- 元素字體大小變化(font-size)
- 添加或者刪除可見的
DOM元素 - 激活
CSS偽類(例如::hover) - 查詢某些屬性或調用某些方法
一些常用且會導致回流的屬性和方法:
| 引起回流屬性和方法 | -- | -- | -- |
|---|---|---|---|
| width | height | margin | padding |
| display | border-width | border | position |
| overflow | font-size | vertical-align | min-height |
| clientWidth | clientHeight | clientTop | clientLeft |
| offsetWidth | offsetHeight | offsetTop | offsetLeft |
| scrollWidth | scrollHeight | scrollTop | scrollLeft |
| scrollIntoView() | scrollTo() | getComputedStyle() | |
| getBoundingClientRect() | scrollIntoViewIfNeeded() |
為什么獲取一些屬性或調用方法也會導致回流?
因為以上屬性和方法都需要返回最新的布局信息,因此瀏覽器不得不觸發回流重繪來返回正確的值。
會導致重繪的屬性
| 屬性: | -- | -- | -- |
|---|---|---|---|
| color | border-style | visibility | background |
| text-decoration | background-image | background-position | background-repeat |
| outline-color | outline | outline-style | border-radius |
| outline-width | box-shadow | background-size |
具體可以在這個網站查找CSS Triggers
瀏覽器的優化機制
由於每次重排都會造成額外的計算消耗,因此大多數瀏覽器都會通過隊列化修改並批量執行來優化重排過程。瀏覽器會將修改操作放入到隊列里,直到過了一段時間或者操作達到了一個閾值,才會進行批量修改並清空隊列。但是,在獲取布局信息的時候,會強制刷新隊列,比如當你訪問以下屬性或者使用以下方法:
-
clientTop、clientLeft、clientWidth、clientHeight
-
offsetTop、offsetLeft、offsetWidth、offsetHeight
-
scrollTop、scrollLeft、scrollWidth、scrollHeight
-
getComputedStyle()
-
getBoundingClientRect
-
具體可以訪問這個網站:paulirish
以上屬性和方法都需要返回最新的布局信息,因此瀏覽器不得不清空隊列,觸發回流重繪來返回正確的值。因此,我們在修改樣式的時候,最好避免使用上面列出的屬性,他們都會刷新渲染隊列。
如何減少回流(Reflow)與重繪(Repaint)?(優化)
合並對DOM樣式的修改,采用css class來修改
const el = document.querySelector('.box')
el.style.margin = '5px'
el.style.borderRadius = '12px'
el.style.boxShadow = '1px 3px 4px #ccc'
建議使用css class
.update{
margin: 5px;
border-dadius: 12px;
box-shadow: 1px 3px 4px #ccc
}
const el = document.querySelector('.box')
el.classList.add('update')
如果需要對DOM進行多次訪問,盡量使用局部變量緩存該DOM
避免使用table布局,可能很⼩的⼀個⼩改動會造成整個table的重新布局
CSS選擇符從右往左匹配查找,避免節點層級過多
DOM離線處理,減少回流重繪次數
離線的DOM不屬於當前DOM樹中的任何一部分,這也就意味着我們對離線DOM處理就不會引起頁面的回流與重繪。
- 使用
display: none,上面我們說到了 (display: none) 將元素從渲染樹中完全移除,元素既不可見,也不是布局的組成部分,之后在該DOM上的操作不會觸發回流與重繪,操作完之后再將display屬性改為顯示,只會觸發這一次回流與重繪。
提醒⏰:visibility : hidden 的元素只對重繪有影響,不影響重排。
- 通過 documentFragment 創建一個
dom文檔片段,在它上面批量操作dom,操作完成之后,再添加到文檔中,這樣只會觸發一次重排。
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const fragment = document.createDocumentFragment();
fruits.forEach(item => {
const li = document.createElement('li');
li.innerHTML = item;
fragment.appendChild(li);
});
el.appendChild(fragment);
- 克隆節點,修改完再替換原始節點
const el = document.querySelector('.box')
const fruits = ['front', 'nanjiu', 'study', 'code'];
const cloneEl = el.cloneNode(true)
fruits.forEach(item => {
const li = document.createElement('li');
li.innerHTML = item;
cloneEl.appendChild(li);
});
el.parentElement.replaceChild(cloneEl,el)
DOM脫離普通文檔流
使用absoult或fixed讓元素脫離普通文檔流,使用絕對定位會使的該元素單獨成為渲染樹中 body 的一個子元素,重排開銷比較小,不會對其它節點造成太多影響。
CSS3硬件加速(GPU加速)
使用css3硬件加速,可以讓transform、opacity、filters這些動畫不會引起回流重繪 。但是對於動畫的其它屬性,比如background-color這些,還是會引起回流重繪的,不過它還是可以提升這些動畫的性能。
常見的觸發硬件加速的css屬性:
- transform
- opacity
- filters
- Will-change
將節點設置為圖層
圖層能夠阻⽌該節點的渲染⾏為影響別的節點。⽐如對於video標簽來說,瀏覽器會⾃動將該節點變為圖層。
推薦閱讀
- Promise、Generator、Async有什么區別?
- 【Vue源碼學習】依賴收集
- 【Vue源碼學習】響應式原理探秘
- JS定時器執行不可靠的原因及解決方案
- 從如何使用到如何實現一個Promise
- 超詳細講解頁面加載過程
原文首發地址點這里,歡迎大家關注公眾號 「前端南玖」,回復進群,拉你進前端交流群一起學習,回復資料,領取前端電子書和學習視頻~。
我是南玖,我們下期見!!!
