//正文開始
關於迴流(reflows)與重繪(repaints),我已經在twitter和delicious上釋出,但是並沒有在演講中提到或是以文章形式釋出。
第一次讓我開始思考關於迴流(reflows)與重繪(repaints)的問題是在和ParisWeb上的Mr. Glazman做一個firey交換的時候。我可能有一些頑固,但是我確實聽了他的一些理論。Stoyan和我開始討論如何量化這個問題。
展望效能社群,除了一些典型的黑盒實驗外,需要與瀏覽器廠商有更多的合作。對於效能,瀏覽器製造者知道哪些是重要的,哪些是不相干的。Opera列出“reflow和repaint是減緩JavaScript的三大主要原因之一”一文,所以其肯定值得一看。// zxx: Firefox瀏覽器相關內容可以看這裡;Safari可以看這裡。
讓我們從一些背景資料開始,當一個元素的外觀的可見性visibility發生改變的時候,重繪(repaint)也隨之發生,但是不影響佈局。類似的例子包括:outline, visibility, or background color。根據Opera瀏覽器,重繪的代價是高昂的,因為瀏覽器必須驗證DOM樹上其他節點元素的可見性。而回流更是效能的關鍵因為其變化涉及到部分頁面(或是整個頁面)的佈局。一個元素的迴流導致了其所有子元素以及DOM中緊隨其後的祖先元素的隨後的迴流。
例如:
<body>
<div class="error">
<h4>我的元件</h4>
<p><strong>錯誤:</strong>錯誤的描述…</p>
<h5>錯誤糾正</h5>
<ol>
<li>第一步</li>
<li>第二步</li>
</ol>
</div>
</body>
在上面的HTML片段中,對該段落(<p>標籤)迴流將會引發強烈的迴流,因為它是一個子節點。這也導致了祖先的迴流(div.error和body – 視瀏覽器而定)。此外,h5和ol也會有簡單的迴流,因為其在DOM中在迴流元素之後。就Opera而言,大部分的迴流將導致頁面的重新渲染。
Opera原文:Reflows are very expensive in terms of performance, and is one of the main causes of slow DOM scripts, especially on devices with low processing power, such as phones. In many cases, they are equivalent to laying out the entire page again.
既然它們對效能影響如此可怕,那什麼會導致迴流呢?
不幸的是,很多的東西,其中一些還與CSS的書寫特別相關。
- 調整視窗大小(Resizing the window)
- 改變字型(Changing the font)
- 增加或者移除樣式表(Adding or removing a stylesheet)
- 內容變化,比如使用者在input框中輸入文字(Content changes, such as a user typing text in
an input box) - 啟用 CSS 偽類,比如 :hover (IE 中為兄弟結點偽類的啟用)(Activation of CSS pseudo classes such as :hover (in IE the activation of the pseudo class of a sibling))
- 操作 class 屬性(Manipulating the class attribute)
- 指令碼操作 DOM(A script manipulating the DOM)
- 計算 offsetWidth 和 offsetHeight 屬性(Calculating offsetWidth and offsetHeight) 根據此可以實現一個jquery外掛,讓元素迴流並重繪。ex. el.style.left=20px; a = el.offsetHeight;el.style.left=22px;
- 設定 style 屬性的值 (Setting a property of the style attribute)
Mozilla關於迴流的文章羅列了導致迴流的要點以及何時可以減少他們。
如何避免迴流或將它們對效能的影響降到最低?
注意:這裡我限定了自己只能討論CSS對迴流的影響,如果您是一位JavaScript程式設計師,我是推薦您讀一下我的reflow連結(zxx: 原作者收藏標記的一些關於reflow的一些文章或頁面連結),有一些非常好的東西,沒有直接關係到CSS。
- 如果想設定元素的樣式,通過改變元素的 class 名 (儘可能在 DOM 樹的最末端)(Change classes on the element you wish to style (as low in the dom tree as possible))
- 避免設定多項內聯樣式(Avoid setting multiple inline styles)
- 應用元素的動畫,使用 position 屬性的 fixed 值或 absolute 值(Apply animations to elements that are position fixed or absolute)
- 權衡平滑和速度(Trade smoothness for speed)
- 避免使用table佈局(Avoid tables for layout)
- 避免使用CSS的JavaScript表示式 (僅 IE 瀏覽器)(Avoid JavaScript expressions in the CSS (IE only))
儘可能在DOM樹的最末端改變class
避免設定多層內聯樣式
動畫效果應用到position屬性為absolute或fixed的元素上
犧牲平滑度換取速度
避免使用table佈局
Jenny Donnelly, YUI 資料表格 widget的所有者,建議使用資料表格的固定佈局以便更有效的佈局演算法,任何表格-佈局的值除了”auto”將引發一個固定佈局,根據CSS2.1規範,這將允許表格一行一行的呈遞。Quirksmode顯示,大部分的瀏覽器對錶格佈局屬性支援良好。
In this manner, the user agent can begin to lay out the table once the entire first row has been received. Cells in subsequent rows do not affect column widths. Any cell that has content that overflows uses the ‘overflow’ property to determine whether to clip the overflow content.
This algorithm may be inefficient since it requires the user agent to have access to all the content in the table before determining the final layout and may demand more than one pass.
避免使用CSS的JavaScript表示式
迴流(reflow)這個名詞指的是網路瀏覽器為了重新渲染部分或全部的文件而重新計算文件中元素的位置和幾何結構的過程。因為迴流(reflow)在瀏覽器中屬於一種使用者主導的模組化操作,所以知道如何去改進迴流(reflow)時間以及知道各種文件屬性(DOM節點深度,css的渲染效率,各種各樣的樣式改變)對迴流(reflow)時間的影響對於開發人員講是很有幫助的。有時候,即使僅僅迴流一個單一的元素,也可能要求它的父元素以及任何跟隨它的元素也產生迴流。
有大量的使用者行為以及潛在的DHTML改變會觸發迴流(reflow)。例如,改變瀏覽器視窗的大小,使用一些JavaScript方法,包括計算樣式,對DOM進行元素的新增或刪除,或是改變元素的class等。值得注意的是有一些操作產生的迴流(reflow)時間可能要比你原先預想的要多——您可以參考下面這張史蒂芬•桑德斯(Steve Souders)的“更快的網站(Even Faster Web Sites)”一文中提到的圖表。
從上邊的表格我們可以清晰的看到並不是所有JavaScript改變的樣式都會在瀏覽器中產生迴流(reflow),所花費的迴流(reflow)時間也是多樣的。而且我們或多或少可以看到,現代瀏覽器在迴流(reflow)時間上要做的更好一些。
藉助Google,我們用各種方式測試了我們頁面以及應用程式的速度,發現在介面上新增功能時迴流是個至關重要必須考慮的因素,我們應盡最大努力傳達生動的,互動的,愉悅的使用者體驗。
指導
以下是一些簡單的指導方針可以幫助你頁面上的迴流(reflow)減到最小。
1.減少不必要的DOM深度。因為無論你改變DOM節點樹上任何一個層級都會影響節點樹的每個層級——從根結點一直到修改的子節點。不必要的節點深度將導致執行迴流時花費更多的時間。
2. 精簡css,去除沒有用處的css
3. 如果你想讓複雜的表現發生改變,例如動畫效果,那麼請在這個流動線之外實現它。使用position-absolute或position-fixed來實現它。
4. 避免不必要的複雜的css選擇符,尤其是使用子選擇器,或消耗更多的CPU去做選擇器匹配。