前言
迴流與重繪對於前端來說可以說是非常重要的知識點了,我們不僅需要知道什麼是迴流與重繪,還需要知道如何進行優化。一個頁面從載入到完成,首先是構建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
- 超詳細講解頁面載入過程
原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,回覆進群,拉你進前端交流群一起學習,回覆資料,領取前端電子書和學習視訊~。
我是南玖,我們下期見!!!