如何減少瀏覽器的 Repaint 和 Reflow


一、什麼是repaint/reflow?

頁面在加載的過程中,需要對文檔結構進行解析,同時需要結合各種各樣的樣式來計算這個頁面長什麼樣子,最後再經過瀏覽器的渲染頁面就出現了。這整個過程細說起來還是比較複雜,其中充滿了repaint和reflow。對於DOM結構中的各個元素都有自己的盒子(模型),這些都需要瀏覽器根據各種樣式(瀏覽器的、開發人員定義的等)來計算並根據計算結果將元素放到它該出現的位置,這個過程稱之爲reflow;當各種盒子的位置、大小以及其他屬性,例如顏色、字體大小等都確定下來後,瀏覽器於是便把這些元素都按照各自的特性繪製了一遍,於是頁面的內容出現了,這個過程稱之爲repaint。

以上提到的只是在頁面加載時必然會出現的repaint和reflow,除此之外,在頁面加載完成後,用戶的一些操作、腳本的一些操作都會導致瀏覽器發生這種行爲,具體在後文闡述。

二、什麼情況下會觸發瀏覽器的repaint/reflow?

除了頁面在首次加載時必然要經歷該過程之外,還有以下行爲會觸發這個行爲:

  • DOM元素的添加、修改(內容)、刪除( Reflow + Repaint)
  • 僅修改DOM元素的字體顏色(只有Repaint,因爲不需要調整佈局)
  • 應用新的樣式或者修改任何影響元素外觀的屬性
  • Resize瀏覽器窗口、滾動頁面
  • 讀取元素的某些屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE))

在繼續下面的文章之前,先介紹一款強大的性能分析工具-dynaTrace,藉助該功能能夠清晰的得到頁面中的資源消耗情況,從而對症下藥。另外,更細節的方面是它可以跟蹤每個函數調用所造成的CPU消耗、Repaint/Reflow。接下來就藉助該工具來測試一下以上描述的幾點情況。

DOM元素的增刪改

先看代碼

  1. <!DOCTYPEhtmlPUBLIC”-//W3C//DTDHTML4.01//EN”“http://www.w3c.org/TR/html4/strict.dtd”>
  2. <html>
  3. <body>
  4. <divid=”test1”οnclick=”addNode()”>這裏是第1個節點</div>
  5. <divid=”test2”οnclick=”modNode()”>這裏是第2個節點</div>
  6. <divid=”test3”οnclick=”delNode()”>這裏是第3個節點</div>
  7. </body>
  8. <scripttype=”text/javascript”>
  9. function$(id){
  10. returndocument.getElementById(id);
  11. }
  12. functionaddNode(){
  13. varn=document.createElement(¹div¹);
  14. n.innerHTML=¹NewNode¹;
  15. $(¹test1¹).appendChild(n);
  16. }
  17. functionmodNode(){
  18. $(¹test2¹).innerHTML=¹hello¹;
  19. }
  20. functiondelNode(){
  21. (¹test3¹).parentNode.removeChild( (¹test3¹));
  22. }
  23. </script>
  24. </html>

 

在依次點擊完每一個按鈕後,我們來看看dynaTrace的情況,首先是一目瞭然的點擊事件分佈

image

放大之後來看一下每個事件的repaint/reflow情況:

增加節點:

image

修改節點:

image

刪除節點:

image

圖中的綠色部分表示的是reflow和repaint過程,其中比較短的綠條標示的reflow過程,後面長條部分表示的是repaint過程。從圖中可以看出,對DOM節點的增刪改都會造成reflow和repaint,由於改動小所以reflow消耗的時間很短,但是由於repaint是全局的,因此消耗的時間都比較長。

修改DOM元素前景色
  1. varn=$(¹colorNode¹);
  2. n.style.color=¹red¹;

 

image

從上圖中可以看到修改字體顏色後,瀏覽器只有repaint而沒有reflow。接下來試試修改背景色:

  1. varn=$(¹colorNode¹);
  2. n.style.backgroundColor=¹red¹;

 

image

由圖中可以看出,修改背景色也會造成reflow和repaint。另外,經過測試發現,只要是修改元素的cssText屬性,不論它的值是什麼,都會導致瀏覽器reflow和repaint,因此在某些時候選擇特定的樣式屬性賦值會有更好的效果。

Resize瀏覽器窗口以及拖動滾動條

image

測試中的操作如下:縮小瀏覽器窗口->放大瀏覽器窗口->拖動頁面滾動條至頁面底部。從圖中可以看到Resize瀏覽器窗口以及拖動滾動條都會造成瀏覽器的repaint,而且CPU的消耗也比較大,尤其是拖動滾動條的時候。

讀取Layout屬性

根據各種參考資料中的描述,在用Javascript讀取DOM節點的Layout屬性(offsetLeft、offsetTop、offsetHeight、offsetWidth、scrollTop/Left/Width/Height、clientTop/Left/Width/Height、getComputedStyle()、currentStyle(in IE)) 的時候也會觸發repaint,不過在以下的測試例子中並沒有發現這一點。

  1. varn=$(¹colorNode¹);
  2. vartemp=document.documentElement.currentStyle;
  3. temp=n.offsetTop;
  4. temp=n.offsetLeft;
  5. temp=n.offsetWidth;
  6. temp=n.offsetHeight;
  7. temp=n.scrollTop;
  8. temp=n.scrollHeight;
  9. alert(temp);

 

image

三、瀏覽器優化

瀏覽器對於每一個渲染動作並不是立即執行,而是維護了一個渲染任務隊列,瀏覽器會根據具體的需要分批集中執行其中的任務。除了瀏覽器自身維護的定期調度之外,腳本中的某些操作會導致瀏覽器立即執行渲染任務,例如讀取元素的Layout屬性。

  1. varbodystyle=document.body.style;
  2. varcomputed;
  3. if(document.body.currentStyle){
  4. computed=document.body.currentStyle;
  5. }else{
  6. computed=document.defaultView.getComputedStyle(document.body,¹¹);
  7. }
  8.  
  9. //每次都讀取
  10.  
  11. bodystyle.color=¹red¹;
  12. bodystyle.padding=¹1px¹;
  13. tmp=computed.backgroundColor;
  14. bodystyle.color=¹white¹;
  15. bodystyle.padding=¹2px¹;
  16. tmp=computed.backgroundImage;
  17. bodystyle.color=¹green¹;
  18. bodystyle.padding=¹3px¹;
  19. tmp=computed.backgroundAttachment;
  20.  
  21. //最後再讀取
  22.  
  23. bodystyle.color=¹yellow¹;
  24. bodystyle.padding=¹4px¹;
  25. bodystyle.color=¹pink¹;
  26. bodystyle.padding=¹5px¹;
  27. bodystyle.color=¹blue¹;
  28. bodystyle.padding=¹6px¹;
  29. tmp=computed.backgroundColor;
  30. tmp=computed.backgroundImage;
  31. tmp=computed.backgroundAttachment;

 

每次讀取的渲染圖:

image

最後讀取的渲染圖:

image

四、如何優化你的腳本來減少reflow/repaint?

1. 避免在document上直接進行頻繁的DOM操作,如果確實需要可以採用off-document的方式進行,具體的方法包括但不完全包括以下幾種:

(1). 先將元素從document中刪除,完成修改後再把元素放回原來的位置

(2). 將元素的display設置爲”none”,完成修改後再把display修改爲原來的值

(3). 如果需要創建多個DOM節點,可以使用DocumentFragment創建完後一次性的加入document

  1. functionappendEveryTime(){
  2. for(vari=5000;i–;){
  3. varn=document.createElement(¹div¹);
  4. n.innerHTML=¹node¹+i;
  5. document.body.appendChild(n);/*每次創建的新節點都append到文檔*/
  6. }
  7. }
  8.  
  9. functionappendLast(){
  10. varfrag=document.createDocumentFragment();
  11. for(vari=5000;i–;){
  12. varn=document.createElement(¹div¹);
  13. n.innerHTML=¹node¹+i;
  14. frag.appendChild(n);/*每次創建的節點先放入DocumentFragment中*/
  15. }
  16. document.body.appendChild(frag);
  17. }

 

用dynaTrace觀察的結果如下,appendLast的性能無論是在Javascript的執行時間以及瀏覽器渲染時間方面都優於appendEveryTime。

appendEveryTime:

image

appendLast:

image

2. 集中修改樣式

(1). 儘可能少的修改元素style上的屬性

(2). 儘量通過修改className來修改樣式

(3). 通過cssText屬性來設置樣式值

如下的代碼中,每一次賦值都會造成瀏覽器重新渲染,可以採用cssText或者className的方式

  1. el.style.color=¹red;
  2. el.style.height=¹100px¹;
  3. el.style.fontSize=¹12px¹;
  4. el.style.backgroundColor=¹white¹;

 

3. 緩存Layout屬性值

對於Layout屬性中非引用類型的值(數字型),如果需要多次訪問則可以在一次訪問時先存儲到局部變量中,之後都使用局部變量,這樣可以避免每次讀取屬性時造成瀏覽器的渲染。

  1. varwidth=el.offsetWidth;
  2. varscrollLeft=el.scrollLeft;

 

4. 設置元素的position爲absolute或fixed

在元素的position爲static和relative時,元素處於DOM樹結構當中,當對元素的某個操作需要重新渲染時,瀏覽器會渲染整個頁面。將元素的position設置爲absolute和fixed可以使元素從DOM樹結構中脫離出來獨立的存在,而瀏覽器在需要渲染時只需要渲染該元素以及位於該元素下方的元素,從而在某種程度上縮短瀏覽器渲染時間,這在當今越來越多的Javascript動畫方面尤其值得考慮。

  1. <bodystyle=”position:relative”>
  2. <divid=”test”style=”background-color:red;width:100px;position:relative;”>AnimationHere</div>
  3. </body>
  4. <scripttype=”text/javascript”>
  5. function$(id){
  6. returndocument.getElementById(id);
  7. }
  8. window.οnlοad=function(){
  9. vart=$(¹test¹);
  10.  
  11. ~function(){
  12. tt.style.left=t.offsetLeft+5+¹px¹;
  13. tt.style.height=t.offsetHeight+5+¹px¹;
  14. setTimeout(arguments.callee,500);
  15. }();
  16. }
  17. </script>

 

通過修改#test元素的postion爲relative和postion分別得到如下兩個測試結果

position: relative

image

position: absolute

image

在postion:relative的測試當中,瀏覽器在重新渲染時做的工作比position:absolute多了不少。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章