你應該要知道的重繪與重排

前言

現代web框架大多都是數據驅動類的,好比 react, vue,因此開發者不須要直接接觸 DOM,修改 data 即可以驅動界面更新。可是做爲前端工程師,瞭解瀏覽器的重繪與重排仍是頗有必要的,能夠幫助咱們寫出更好性能的 web 應用。css

瀏覽器的渲染

  • CSS Tree: 瀏覽器將 CSS 解析成 CSSOM 的樹形結構
  • DOM Tree:瀏覽器將 HTML 解析成樹形的數據結構
  • Render Tree:將 DOM 與 CSSOM 合併成一個渲染樹

有了渲染樹(Render Tree),瀏覽器就知道網頁中有哪些節點,以及各個節點與 CSS 的關係,從而知道每一個節點的位置和幾何屬性,而後繪製頁面。前端

重繪與重排

當 DOM 的變化影響了元素的幾何屬性(好比 width 和 height ),就會致使瀏覽器從新計算元素的幾何屬性,一樣受到該元素影響的其餘元素也會發生從新計算。此時,瀏覽器會使渲染樹中受到影響的部分失效,並從新構造渲染樹。這個過程被稱爲重排(也叫「迴流」)(reflow),完成重排以後,瀏覽器會從新繪製受影響的部分到頁面上,這個過程就是重繪(repaint)。vue

因此重排必定會引發重繪,而重繪不必定會引發重排,好比一個元素的改變並無影響佈局的改變(background-color的改變),在這種狀況下,只會發生一個重繪(不須要重排)。react

引發重排的因素

能夠總結出,當元素的幾何屬性或頁面佈局發生改變就會引發重排,好比:web

  • 對可見 DOM 元素的操做(添加,刪除或順序變化)
  • 元素位置發生改變
  • 元素的幾何屬性發生改變(好比:外邊距、內邊距、邊框寬度以及內容改變引發的寬高的改變)
  • 頁面首次渲染
  • 僞類樣式激活(hover等)
  • 瀏覽器視口尺寸發生改變(滾動或縮放)

如何優化

重繪與重排都是代價昂貴的操做(由於每次重排都會產生計算消耗),它們會致使 web 應用的 UI 反應遲鈍,因此開發者在編寫應用程序的時候應當儘可能減小這類過程的發生。瀏覽器

渲染樹隊列

由於過多的重繪與重排可能會致使應用的卡頓,因此瀏覽器會對這個有一個優化的過程。大多數瀏覽器會經過隊列化來批量執行(好比把腳本對 DOM 的修改放入一個隊列,在隊列全部操做都結束後再進行一次繪製)。可是開發者有時可能不知不覺的強制刷新渲染隊列來當即進行重排重繪,好比獲取頁面佈局信息會致使渲染隊列的強制刷新,如下屬性或方法會當即觸發頁面繪製:緩存

  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()

以上屬性和方法都是要瀏覽器返回最新的佈局信息,因此瀏覽器會馬上執行渲染隊列中的「待處理變化」, 並觸發重排重繪而後返回最新的值。因此在修改樣式的過程當中,應該儘可能避免使用以上屬性和方法。前端工程師

減小重繪與重排

爲了減小重繪重排的發生次數,開發者應該合併屢次對 DOM 的修改和對樣式的修改,而後一次性處理。數據結構

合併樣式操做

好比:app

var el = document.querySelector('div');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

能夠合併成:

var el = document.querySelector('div');
el.style.cssText = 'border-left: 1px; border-right: 1px; padding: 5px;'

批量修改DOM

使元素脫離文檔流,再對其進行操做,而後再把元素帶回文檔中,這種辦法能夠有效減小重繪重排的次數。有三種基本辦法能夠使元素脫離文檔流:

隱藏元素,應用修改,從新顯示
var ul = document.querySelector('ul');
ul.style.display = 'none';
// code... 對ul進行DOM操做
ul.style.display = 'block';
使用文檔片斷(document fragment),構建一個空白文檔進行 DOM 操做,而後再放回原文檔中
var fragment = document.createDocumentFragment();
// code... 對fragment進行DOM操做
var ul = document.querySelector('ul');
ul.appendChild(fragment)
拷貝要修改的元素到一個脫離文檔流的節點中,修改副本,而後再替換原始元素
var ul = document.querySelector('ul');
var cloneUl = ul.cloneNode(true);
// code... 對clone節點進行DOM操做
ul.parentNode.replaceChild(cloneUl, ul)

緩存佈局信息

前面已經知道,獲取頁面佈局信息,會致使瀏覽器強制刷新渲染隊列。因此減小這些操做是很是有必要的,開發者能夠將第一次獲取到的頁面信息緩存到局部變量中,而後再操做局部變量,好比下面的僞代碼示例:

// 低效的
element.style.left = 1 + element.offsetLeft + 'px';
element.style.top = 1 + element.offsetTop + 'px';
if (element.offsetTop > 500) {
    stopAnimation();
}
// 高效的
var offsetLeft = element.offsetLeft;
var offsetTop = element.offsetTop;
offsetLeft++;
offsetTop++;
element.style.left = offsetLeft + 'px';
element.style.top = offsetTop + 'px';
if (offsetTop > 500) {
    stopAnimation();
}

總結

爲了減小重繪重排帶來的性能消耗,能夠經過如下幾點改善 web 應用:

  1. 批量修改 DOM 和樣式
  2. 「離線」操做 DOM 樹,脫離文檔流
  3. 緩存到局部變量,減小頁面佈局信息的訪問次數

參考

高性能JavaScript

相關文章
相關標籤/搜索