天天看點

前端優化,了解浏覽器重排與重繪 觸發重排浏覽器的優化:渲染隊列重繪與重排的性能優化

2018年3月15日

當DOM變化影響了元素的幾何屬性(寬、高改變等等) 

浏覽器此時需要重新計算元素幾何屬性 

并且頁面中其他元素的幾何屬性可能會受影響 

這樣渲染樹就發生了改變,也就是重新構造RenderTree渲染樹 

這個過程叫做重排(reflow)

如果DOM變化僅僅影響的了背景色等等非幾何屬性 

此時就發生了重繪(repaint)而不是重排 

因為布局沒有發生改變

頁面發生重繪和重排,都會影響性能(重繪還好一些),盡量避免或減少重排

觸發重排

頁面布局和元素幾何屬性的改變就會導緻重排 

下列情況會發生重排

  • 頁面初始渲染
  • 添加/删除可見DOM元素
  • 改變元素位置
  • 改變元素尺寸(寬、高、内外邊距、邊框等)
  • 改變元素内容(文本或圖檔等)
  • 改變視窗尺寸

不同的條件下發生重排的範圍及程度會不同 

某些情況甚至會重排整個頁面,比如滑動滾動條

浏覽器的優化:渲染隊列

  1.     div.style.left = '10px';
  2.     div.style.top = '10px';
  3.     div.style.width = '20px';
  4.     div.style.height = '20px';

如上,修改了元素的left、top、width、height屬性 

滿足我們發生重排的條件 

理論上會發生4次重排 

但是實際上隻會發生1次重排 

這是因為我們現代的浏覽器都有渲染隊列的機制 

當我改變了元素的一個樣式會導緻浏覽器發生重排或重繪時 

它會進入一個渲染隊列 

然後浏覽器繼續往下看,如果下面還有樣式修改 

那麼同樣入隊 

直到下面沒有樣式修改 

浏覽器會按照渲染隊列批量執行來優化重排過程,一并修改樣式 

這樣就把本該4次的重排優化為1次

    console.log(div.offsetLeft);

    console.log(div.offsetTop);

    console.log(div.offsetWidth);

    console.log(div.offsetHeight);

如上,發生4次重排

offsetLeft/Top/Width/Height 會強制重新整理隊列要求樣式修改任務立刻執行 

這麼做是有道理的 

畢竟浏覽器不确定在接下來的代碼中你是否還會修改同樣的樣式 

為了保證獲得正确的值,它不得不立刻執行渲染隊列觸發重排

以下屬性或方法會重新整理渲染隊列

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

我們在修改樣式過程中,要盡量避免使用上面的屬性

重繪與重排的性能優化

分離讀寫操作

了解了原理我們就可以對上面的代碼進行優化

  1. div.style.left = '10px';
  2. div.style.top = '10px';
  3. div.style.width = '20px';
  4. div.style.height = '20px';
  5. console.log(div.offsetLeft);
  6. console.log(div.offsetTop);
  7. console.log(div.offsetWidth);
  8. console.log(div.offsetHeight);

僅僅發生1次重排了,原因相信大家已經很清晰了 

把所有的讀操作移到所有寫操作之後 

樣式集中改變

還是我們最初修改樣式的代碼

如上,雖然現代浏覽器有渲染隊列的優化機制

但是古董浏覽器效率仍然低下,觸發了4次重排 

即便這樣,我們仍然可以做出優化 

我們需要cssText屬性合并所有樣式改變

    div.style.cssText = 'left:10px;top:10px;width:20px;height:20px;';

如上,隻需要修改DOM一次一并處理 

僅僅觸發了1次重排 

而且隻用了一行代碼,看起來相對幹淨一些

注意,cssText會覆寫已有的行間樣式 

如果想保留原有行間樣式,如下

    div.style.cssText += ';left:10px;';

除了cssText以外,我們還可以通過修改class類名來進行樣式修改,如下

div.className = 'new-class';

這種辦法可維護性好,還可以幫助我們免除顯示性代碼 

有很小的性能影響,改變class需要檢查級聯樣式,但是符合BEM标準可以更好的解耦,增加可維護性,建議這樣寫

div.className = 'js-class';

緩存布局資訊

我覺得緩存真是萬金油,哪種性能優化都少不了它

    div.style.left = div.offsetLeft + 1 + 'px';

    div.style.top = div.offsetTop + 1 + 'px';

如上,這種讀操作完就執行寫操作造成了2次重排 

緩存可以進行優化

    var curLeft = div.offsetLeft;

    var curTop = div.offsetTop;

    div.style.left = curLeft + 1 + 'px';

    div.style.top = curTop + 1 + 'px';

如上,這也相當于是分離讀寫操作了 

優化為1次重排

元素批量修改

現在我們想要向ul中循環添加大量li 

(如果ul還不存在,最好的辦法是先循環添加li到ul,然後再把ul添加到文檔,1次重排)

var ul = document.getElementById('demo');

for(var i = 0; i < 1e5; i++){

    var li = document.createElement('li');

    var text = document.createTextNode(i);

    li.appendChild(text); ul.appendChild(li);

}

我可以做出下面的優化

ul.style.display = 'none';

    for(var i = 0; i < 1e5; i++){

        var li = document.createElement('li');

        var text = document.createTextNode(i);

        li.appendChild(text); ul.appendChild(li);

    }

ul.style.display = 'block';

var frg = document.createDocumentFragment();

    li.appendChild(text); frg.appendChild(li);

ul.appendChild(frg);

var clone = ul.cloneNode(true); 

    li.appendChild(text); clone.appendChild(li); 

ul.parentNode.replaceChild(clone,ul);

上面的方法減少重繪和重排的原理很簡單

  • 元素脫離文檔
  • 改變樣式
  • 元素回歸文檔

而改變元素就分别使用了

隐藏元素

文檔碎片

克隆元素 

繼續閱讀