回流(reflow)與重繪(repaint)


最近項目排期不緊,於是看了一下之前看了好久也沒看明白的chrome調試工具的timeline。但是很遺憾,雖然大概懂了每一項是做什么的,但是用起來並不能得心應手。所以今天的重點不是timeline,而是我在看timeline的時候發現的reflow.reflow,回流。什么是回流呢?看了好多文章都提到回流與重繪會影響頁面性能,每次這兩個都會被同時提及,關系就好像KFC邊上一定會有MC一樣親密的讓人摸不到頭腦。然后看了許多資料之后終於有了自己的認知,先列出我的參考文章

http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/comment-page-1/

http://www.css88.com/archives/4991

http://www.css88.com/archives/4996

下面的內容是我看過這些資料之后自己的一些理解及總結。

一.明確概念

首先我們要明確頁面在文檔加載完成之后到完全顯示中間的過程是1.根據文檔生成DOM樹(包括display:none的節點)2.在DOM樹基礎上根據節點的幾何屬性(margin/padding/width/height等)生成render樹(不包括display:none、head節點但會包含visibility:hidden節點)3.在render樹基礎上進行進一步渲染包括color,outline等樣式

reflow:當render樹中的一部分或者全部因為大小邊距等問題發生改變而需要重建的過程叫做回流

repaint:當元素的一部分屬性發生變化,如外觀背景色不會引起布局變化而需要重新渲染的過程叫做重繪

二.什么會引起回流

個問題其實很簡單,什么引起的,看定義不就可以了?

問題是引起的原因可能會很多

籠統來說當頁面的布局和幾何屬性發生變化的時候就會引起回流。具體來說大概分別5大類:

1.首當其沖自然是dom樹結構變化,比如你刪除或者添加某個node.

2.元素幾何屬性變化,包括margin,padding,height,width,border等

3.頁面渲染初始化

4.獲取某些屬性。雖然瀏覽器引擎可能會針對重排做了優化,比如Opera,它會等到有足夠 數量的變化發生,或者等到一定的時間,或者等一個線程結束,再一起處理,這樣就只發生一次重排。但除了render樹的直接變化,當獲取一些屬性時,瀏覽器為取得 正確的值也會觸發回流。這樣就使得瀏覽器的優化失效了。這些屬性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用這些值時應進行緩存。(這段我是直接引用的。。。)

5.瀏覽器窗口發生變化-resize事件發生時

以上,其實理解起來很容易。所謂的render樹就是識別了幾何屬性的dom數,好像我們畫人體的時候,dom樹是先確定都有什么比如四肢,頭部,身體,其他器官等;而render樹則是確定這個人的高矮胖瘦,頭發是否蓋眼睛等,如果我們在繪畫過程中發現脖子長了那就慘了,脖子下面都要重畫。這就是回流了。如果發現只是手指畫的有問題也還是要回流但我們只需要重畫手指。(當然,我說的是手就是手,沒什么特別造型的時候);當我們的render樹完事了,也就是人體大概輪廓我們都畫好了,就可以上色了,換個發色這種我們叫重繪。

var s = document.body.style;
s.padding = "2px"; // 回流+重繪
s.border = "1px solid red"; // 再一次 回流+重繪
s.color = "blue"; // 再一次重繪
s.backgroundColor = "#ccc"; // 再一次 重繪
s.fontSize = "14px"; // 再一次 回流+重繪
// 添加node,再一次 回流+重繪
document.body.appendChild(document.createTextNode('abc!'));

  現在我們大概都能得出的結論是:回流比重繪的代價要高,至於具體的花銷跟render樹有多少節點需要重新構建有關。

  還有就是,回流一定會伴隨着重繪,但是重繪不一定會引起回流。

三、我們要如何避免

從上面的實例代碼中可以看到一共七行代碼引起了6次左右的回流、重繪(上面的代碼我大老遠從別的頁面拿過來當然不只是就用那一次,哈哈),而且我們剛剛還知道了回流花銷真是不小,那么瀏覽器是不是真的每次js語句引起了回流他就執行一下呢?恩,后面的內容我是看的參考資料的:等隊列中的操作到了一定的數量或者到了一定的時間間隔,瀏覽器就會flush隊列,進行一個批處理。這樣就會讓多次的回流、重繪變成一次回流重繪。

但是盡管瀏覽器挺機智地幫我們優化了代碼,我們自己作死也是沒救的,比如你去請求

1. offsetTop, offsetLeft, offsetWidth, offsetHeight

2. scrollTop/Left/Width/Height

3. clientTop/Left/Width/Height

4. width,height

5. 請求了getComputedStyle(), 或者 IE的 currentStyle

瀏覽器為了給你返回一個比較精確的答案,他會提前flush隊列,因為隊列中可能會有影響這些值的操作。

所以我們可以做的是: 

1.將那些改變樣式的操作集合在一次完事,直接改變className或者cssText

2.讓要操作的元素進行離線處理,處理完事以后再一起更新

(1)使用DocumentFragment進行緩存操作,引發一次回流和重繪

課外延伸

DocumentFragment 節點不屬於文檔樹,繼承的 parentNode 屬性總是 null。

不過它有一種特殊的行為,該行為使得它非常有用,即當請求把一個 DocumentFragment 節點插入文檔樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。這使得 DocumentFragment 成了有用的占位符,暫時存放那些一次插入文檔的節點。它還有利於實現文檔的剪切、復制和粘貼操作。

其實他就是一個游離在DOM樹外面的容器,所以你在把它插入文檔節點之前,隨便給他增刪節點都不會引起回流

(2)使用display:none,只引發兩次回流和重繪。道理跟上面的一樣。因為display:none的元素不會出現在render樹

(3)使用cloneNode和replaceChild技術,引發一次回流和重繪(這條其實沒太明白)

3.不要經常訪問會引起瀏覽器flush隊列的屬性,非要高頻訪問的話建議緩存到變量;

4.將需要多次重排的元素,position屬性設為absolute或fixed,這樣此元素就脫離了文檔流,它的變化不會影響到其他元素。例如有動畫效果的元素就最好設置為絕對定位;

5.盡量不要使用表格布局,如果沒有定寬表格一列的寬度由最寬的一列決定,那么很可能在最后一行的寬度超出之前的列寬,引起整體回流造成table可能需要多次計算才能確定好其在渲染樹中節點的屬性,通常要花3倍於同等元素的時間。

四、總結

別人這個地方都是列了一下實驗結果,鑒於我的timeline還用不明白就不獻丑了。

如有錯誤之處,歡迎指正。

最后再強調一下,以上內容參考了文章開頭列出的資料,如有雷同,那很正常。。。


免責聲明!

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



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