瀏覽器重繪(repaint)重排(reflow)與優化[瀏覽器機制]

OBKoro1發表於2018-12-24

很多人都知道要減少瀏覽器的重排和重繪,但對其中的具體原理以及如何具體操作並不是很瞭解,當突然提起這個話題的時候,還是會一臉懵逼。希望大家可以耐著性子閱讀本文,仔細琢磨,徹底掌握這個知識點!

部落格前端積累文件公眾號GitHub

網頁生成過程:

  1. HTML被HTML解析器解析成DOM 樹
  2. css則被css解析器解析成CSSOM 樹
  3. 結合DOM樹和CSSOM樹,生成一棵渲染樹(Render Tree)
  4. 生成佈局(flow),即將所有渲染樹的所有節點進行平面合成
  5. 將佈局繪製(paint)在螢幕上

第四步和第五步是最耗時的部分,這兩步合起來,就是我們通常所說的渲染

網上找了一張圖片,我加了註釋會更直觀一些:

瀏覽器重繪(repaint)重排(reflow)與優化[瀏覽器機制]


渲染:

網頁生成的時候,至少會渲染一次

在使用者訪問的過程中,還會不斷重新渲染

重新渲染需要重複之前的第四步(重新生成佈局)+第五步(重新繪製)或者只有第五個步(重新繪製)。

重排比重繪大:

大,在這個語境裡的意思是:誰能影響誰?

  • 重繪:某些元素的外觀被改變,例如:元素的填充顏色
  • 重排:重新生成佈局,重新排列元素。

就如上面的概念一樣,單單改變元素的外觀,肯定不會引起網頁重新生成佈局,但當瀏覽器完成重排之後,將會重新繪製受到此次重排影響的部分

比如改變元素高度,這個元素乃至周邊dom都需要重新繪製。
複製程式碼

也就是說:"重繪"不一定會出現"重排","重排"必然會出現"重繪"

重排(reflow):

概念:

當DOM的變化影響了元素的幾何資訊(DOM物件的位置和尺寸大小),瀏覽器需要重新計算元素的幾何屬性,將其安放在介面中的正確位置,這個過程叫做重排。

重排也叫回流,重排的過程以下面這種理解方式更清晰一些:

迴流就好比向河裡(文件流)扔了一塊石頭(dom變化),激起漣漪,然後引起周邊水流受到波及,所以叫做迴流

常見引起重排屬性和方法

任何會改變元素幾何資訊(元素的位置和尺寸大小)的操作,都會觸發重排,下面列一些栗子:

  1. 新增或者刪除可見的DOM元素;
  2. 元素尺寸改變——邊距、填充、邊框、寬度和高度
  3. 內容變化,比如使用者在input框中輸入文字
  4. 瀏覽器視窗尺寸改變——resize事件發生時
  5. 計算 offsetWidth 和 offsetHeight 屬性
  6. 設定 style 屬性的值
常見引起重排屬性和方法
width height margin padding
display border position overflow
clientWidth clientHeight clientTop clientLeft
offsetWidth offsetHeight offsetTop offsetLeft
scrollWidth scrollHeight scrollTop scrollLeft
scrollIntoView() scrollTo() getComputedStyle()
getBoundingClientRect() scrollIntoViewIfNeeded()

重排影響的範圍:

由於瀏覽器渲染介面是基於流失佈局模型的,所以觸發重排時會對周圍DOM重新排列,影響的範圍有兩種:

  • 全域性範圍:從根節點html開始對整個渲染樹進行重新佈局。
  • 區域性範圍:對渲染樹的某部分或某一個渲染物件進行重新佈局

全域性範圍重排

<body>
  <div class="hello">
    <h4>hello</h4>
    <p><strong>Name:</strong>BDing</p>
    <h5>male</h5>
    <ol>
      <li>coding</li>
      <li>loving</li>
    </ol>
  </div>
</body>
複製程式碼

當p節點上發生reflow時,hello和body也會重新渲染,甚至h5和ol都會收到影響。

區域性範圍重排:

用區域性佈局來解釋這種現象:把一個dom的寬高之類的幾何資訊定死,然後在dom內部觸發重排,就只會重新渲染該dom內部的元素,而不會影響到外界。

儘可能的減少重排的次數、重排範圍:

重排需要更新渲染樹,效能花銷非常大:

它們的代價是高昂的,會破壞使用者體驗,並且讓UI展示非常遲緩,我們需要儘可能的減少觸發重排的次數。

重排的效能花銷跟渲染樹有多少節點需要重新構建有關係:

所以我們應該儘量以區域性佈局的形式組織html結構,儘可能小的影響重排的範圍。

而不是像全域性範圍的示例程式碼一樣一溜的堆砌標籤,隨便一個元素觸發重排都會導致全域性範圍的重排。

重繪(repaint):

概念

當一個元素的外觀發生改變,但沒有改變佈局,重新把元素外觀繪製出來的過程,叫做重繪。

常見的引起重繪的屬性:

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

瀏覽器的渲染佇列:

思考以下程式碼將會觸發幾次渲染?

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
複製程式碼

根據我們上文的定義,這段程式碼理論上會觸發4次重排+重繪,因為每一次都改變了元素的幾何屬性,實際上最後只觸發了一次重排,這都得益於瀏覽器的渲染佇列機制

當我們修改了元素的幾何屬性,導致瀏覽器觸發重排或重繪時。它會把該操作放進渲染佇列,等到佇列中的操作到了一定的數量或者到了一定的時間間隔時,瀏覽器就會批量執行這些操作。

強制重新整理佇列:

div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
複製程式碼

這段程式碼會觸發4次重排+重繪,因為在console中你請求的這幾個樣式資訊,無論何時瀏覽器都會立即執行渲染佇列的任務,即使該值與你操作中修改的值沒關聯。

因為佇列中,可能會有影響到這些值的操作,為了給我們最精確的值,瀏覽器會立即重排+重繪

強制重新整理佇列的style樣式請求

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop, scrollLeft, scrollWidth, scrollHeight
  3. clientTop, clientLeft, clientWidth, clientHeight
  4. getComputedStyle(), 或者 IE的 currentStyle

我們在開發中,應該謹慎的使用這些style請求,注意上下文關係,避免一行程式碼一個重排,這對效能是個巨大的消耗

重排優化建議

就像上文提到的我們要儘可能的減少重排次數、重排範圍,這樣說很泛,下面是一些行之有效的建議,大家可以參考一下。

1. 分離讀寫操作

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
複製程式碼

還是上面觸發4次重排+重繪的程式碼,這次只觸發了一次重排:

在第一個console的時候,瀏覽器把之前上面四個寫操作的渲染佇列都給清空了。剩下的console,因為渲染佇列本來就是空的,所以並沒有觸發重排,僅僅拿值而已。

2. 樣式集中改變

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
複製程式碼

雖然現在大部分瀏覽器有渲染佇列優化,不排除有些瀏覽器以及老版本的瀏覽器效率仍然低下:

建議通過改變class或者csstext屬性集中改變樣式

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";
// good 
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
複製程式碼

3. 快取佈局資訊

// bad 強制重新整理 觸發兩次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';

// good 快取佈局資訊 相當於讀寫分離
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
複製程式碼

4. 離線改變dom

  • 隱藏要操作的dom

    在要操作dom之前,通過display隱藏dom,當操作完成之後,才將元素的display屬性為可見,因為不可見的元素不會觸發重排和重繪。

    dom.display = 'none'
    // 修改dom樣式
    dom.display = 'block'
    複製程式碼
  • 通過使用DocumentFragment建立一個dom碎片,在它上面批量操作dom,操作完成之後,再新增到文件中,這樣只會觸發一次重排。

  • 複製節點,在副本上工作,然後替換它!

5. position屬性為absolute或fixed

position屬性為absolute或fixed的元素,重排開銷比較小,不用考慮它對其他元素的影響

6. 優化動畫

  • 可以把動畫效果應用到position屬性為absolute或fixed的元素上,這樣對其他元素影響較小

    動畫效果還應犧牲一些平滑,來換取速度,這中間的度自己衡量:

    比如實現一個動畫,以1個畫素為單位移動這樣最平滑,但是reflow就會過於頻繁,大量消耗CPU資源,如果以3個畫素為單位移動則會好很多。

  • 啟用GPPU加速

    此部分來自優化CSS重排重繪與瀏覽器效能

    GPU(影象加速器):

    GPU 硬體加速是指應用 GPU 的圖形效能對瀏覽器中的一些圖形操作交給 GPU 來完成,因為 GPU 是專門為處理圖形而設計,所以它在速度和能耗上更有效率。

    GPU 加速通常包括以下幾個部分:Canvas2D,佈局合成, CSS3轉換(transitions),CSS3 3D變換(transforms),WebGL和視訊(video)。

    /*
     * 根據上面的結論
     * 將 2d transform 換成 3d
     * 就可以強制開啟 GPU 加速
     * 提高動畫效能
     */
    div {
      transform: translate3d(10px, 10px, 0);
    }
    複製程式碼

結語

重排也是導致DOM指令碼執行效率低的關鍵因素之一,重排與重繪作為大廠經常出現的面試題,並且涉及的效能優化,這是前端必須掌握的基本概念/技能之一(敲黑板!)。

重排會不斷觸發這是不可避免的,但我們在開發時,應儘量按照文中的建議來組織程式碼,這種優化,需要平時有意識的去做,一點一滴的去做,希望大家重視一下。

希望看完的朋友可以點個喜歡/關注,您的支援是對我最大的鼓勵。

部落格前端積累文件公眾號GitHub

以上2018.12.17

參考資料:

網頁效能管理詳解

優化CSS重排重繪與瀏覽器效能

相關文章