介紹迴流與重繪(Reflow & Repaint),以及如何進行優化?

前端南玖發表於2022-02-14

前言

迴流與重繪對於前端來說可以說是非常重要的知識點了,我們不僅需要知道什麼是迴流與重繪,還需要知道如何進行優化。一個頁面從載入到完成,首先是構建DOM樹,然後根據DOM節點的幾何屬性形成render樹(渲染樹),當渲染樹構建完成,頁面就根據DOM樹開始佈局了,渲染樹也根據設定的樣式對應的渲染這些節點。在這個過程中,迴流與DOM樹,渲染樹有關,重繪與渲染樹有關。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新的文章~

頁面渲染過程

render.png

  • 解析HTML構建DOM Tree
  • 解析CSS構建CSSOM Tree
  • 構建渲染樹(Render Tree),渲染樹?只包含渲染網頁所需的節點

為構建渲染樹,瀏覽器大體上完成了下列工作:

  1. 從 DOM 樹的根節點開始遍歷每個可見節點。
    • 某些節點不可見(例如指令碼標記、元標記等),因為它們不會體現在渲染輸出中,所以會被忽略。
    • 某些節點通過 CSS 隱藏(例如display: none),因此在渲染樹中也會被忽略。
  2. 對於每個可見節點,為其找到適配的 CSSOM 規則並應用它們。
  3. 發射可見節點,連同其內容和計算的樣式。

Note: 請注意 visibility: hiddendisplay: none 是不一樣的。前者隱藏元素,但元素仍佔據著佈局空間(即將其渲染成一個空框),而後者 (display: none) 將元素從渲染樹中完全移除,元素既不可見,也不是佈局的組成部分。

最終輸出的渲染同時包含了螢幕上的所有可見內容及其樣式資訊。有了渲染樹,我們就可以進入“佈局”階段。

  • 佈局計算每個DOM物件的精確位置和大小
  • 渲染(繪製,合成),使用最終渲染樹將畫素渲染到螢幕上

有關頁面渲染的過程可以看我之前的文章:超詳細講解頁面載入過程,這裡我們把重點放在重繪迴流上。

什麼是迴流(Reflow)與重繪(Repaint)?

迴流(Reflow)

當渲染樹render tree中的一部分(或全部)因為元素的規模尺寸,佈局,隱藏等改變而需要重新構建。這就稱為迴流(reflow)。每個頁面至少需要一次迴流,就是在頁面第一次載入的時候,這時候是一定會發生迴流的,因為要構建render tree。在迴流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並重新構造這部分渲染樹,完成迴流後,瀏覽器會重新繪製受影響的部分到螢幕中,該過程稱為重繪

簡單來說,迴流就是計算元素在裝置內的確切位置和大小並且重新繪製

迴流的代價要遠大於重繪。並且迴流必然會造成重繪,但重繪不一定會造成迴流。

重繪(Repaint)

當渲染樹render tree中的一些元素需要更新樣式,但這些樣式屬性只是改變元素的外觀,風格,而不會影響佈局的,比如background-color。則就叫稱為重繪(repaint)

簡單來說,重繪就是將渲染樹節點轉換為螢幕上的實際畫素,不涉及重新佈局階段的位置與大小計算

為什麼不建議頻繁操作DOM?

我們都知道操作DOM其實是非常耗效能的,所以我們不僅要避免去操作DOM,還要減少訪問DOM的次數。

因為在瀏覽器中,DOMJS的實現,並不是在同一個引擎中完成的。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脫離普通文件流

使用absoultfixed讓元素脫離普通文件流,使用絕對定位會使的該元素單獨成為渲染樹中 body 的一個子元素,重排開銷比較小,不會對其它節點造成太多影響。

CSS3硬體加速(GPU加速)

使用css3硬體加速,可以讓transform、opacity、filters這些動畫不會引起迴流重繪 。但是對於動畫的其它屬性,比如background-color這些,還是會引起迴流重繪的,不過它還是可以提升這些動畫的效能。

常見的觸發硬體加速的css屬性:

  • transform
  • opacity
  • filters
  • Will-change

將節點設定為圖層

圖層能夠阻⽌該節點的渲染⾏為影響別的節點。⽐如對於video標籤來說,瀏覽器會⾃動將該節點變為圖層。

推薦閱讀

原文首發地址點這裡,歡迎大家關注公眾號 「前端南玖」,回覆進群,拉你進前端交流群一起學習,回覆資料,領取前端電子書和學習視訊~。

我是南玖,我們下期見!!!

相關文章