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


前言

回流與重繪對於前端來說可以說是非常重要的知識點了,我們不僅需要知道什么是回流與重繪,還需要知道如何進行優化。一個頁面從加載到完成,首先是構建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標簽來說,瀏覽器會⾃動將該節點變為圖層。

推薦閱讀

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM