關於瀏覽器中DOM操作的效能最佳化,在上一篇博文《瀏覽器中DOM操作的效能最佳化(一)》中已經闡述了訪問和修改DOM元素對效能的影響及最佳化方案。這次我們就來說一下關於頁面的重繪和重排版問題。
當瀏覽器下載完所有的HTML標籤和其組件(Javascript,css,圖片等)後,瀏覽器就會解析檔案並建立兩個內部資料結構:
1、DOM Tree :表示頁面的結構
2、Render Tree :表示DOM樹如何顯示
在渲染樹仲,每個DOM節點(隱藏的節點除外)都有至少一個相對應的節點。渲染樹的節點被稱為“盒”,符合CSS定義的盒子模型——一個具有填充(padding)、邊框(border)、邊距(margin)和位置(position)的盒子。一旦DOM樹和渲染樹構造完畢,瀏覽器就可以顯示(繪製)頁面上的元素。
當DOM的元素幾何屬性(寬和高)改變的時候,瀏覽器就是重新計算元素的幾何屬性,而其它元素的幾何屬性和位置也可能會因此受到影響,對應的渲染樹部分因此失效,瀏覽器就不得不重構渲染樹,這個過程就叫做重排版。重排版完成時,瀏覽器會在一個重繪進程中繪製螢幕上受影響的部分。而如果沒有改變元素的幾何屬性(如僅改變元素的背景),元素的布局沒有受到影響,這個過程就叫做重繪。下述情況中會發生重排版:
- 添加或刪除可見的元素
- 元素的位置改變
- 元素的尺寸改變
- 元素的內容改變
- 瀏覽器視窗大小改變
調用某些方法或訪問某些屬性也會引發重排版,因為需要計算布局資訊,所以瀏覽器不得不運行渲染隊列中待改變的項目並重新排版以返回正確的值。所以當要用到下列的屬性或方法時,最好用局部變數緩衝它們
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- document.defaultView.getComputedStyle() (IE中對應的元素屬性是currentStyle)
既然重繪和重排版會帶來效能影響,我們就應該避免次情況發生的次數。
修改style屬性
考慮下面的這個列子:
var el = docuemnt.getElementById("mydiv");el.style.borderLeft = "1px";el.style.margin = "5px";el.style.padding = "5px";
可以最佳化成如下代碼:
var el = document.getElementById("mydiv");el.style.cssText = ";border:1px;margin:5px;padding:5px";//注意前面有個分號,避免覆蓋之前的樣式
DOM元素的批量修改
當你需要對DOM元素進行多次修改時,可通過以下的步驟減少重繪和重排版的次數:
- 從文檔流中移除該DOM元素(引發重排版)
- 對該元素進行多次修改(如果其它兩步,則每一次修改都會導致重排版)
- 將元素帶迴文檔中顯示(引發重排版)
有二種方法可以將DOM元素從文檔流中移除:
- 隱藏元素,進行修改,然後再顯示
- 新建立或複製節點,進行修改,然後覆蓋原始的DOM元素
下面就舉個列子說明(更新列表):
HTML code:
<ul id="mylist"> <li><a href="http://www.baidu.com">baidu</a></li> <li><a href="http://www.google.com.hk">google</a></li></ul>
Javascript code:
//要添加的資料var data = [ { name:"cnblogs", url:"http://www.cnblogs.com/" }, { name:"leolai", url:"http://www.cnblogs.com/leolai/" }];//建立一個通用的函數,用於將資料添加到指定的列表中function appendDataToList(targetElement, data){ var a, li, item; for(var i=0,len=data.length; i<len; i++){ item = data[i]; a = document.createElement("a"); a.src = item.url; a.appendChild(document.createTextNode(item.name)); li = document.createElement("li"); li.appendChild(a); targetElement.appendChild(li); }}//如果不理會重排版問題,將資料更新到列表中最簡單的方法如下var ul = document.getElementById("mylist");appendDataToList(ul,data);//使用這個方法,當data中每個項目追加到ul中都會導致重排版//所以根據上面提到的最佳化方法,可修改成如下代碼
//修改一:先隱藏,再修改,最後顯示var ul = document.getElementById("mylist"); ul.style.display = "none";appendDataToList(ul,data);ul.style.display = "block";//修改二:利用文檔片段做一次性更新(推薦使用)var fragment = document.createDocumentFragment();appendDataToList(fragment,data);document.getElementById("mylist").appendChild(fragment);//修改三:複製,修改副本,覆蓋var ul = document.getElementById("mylist");var clone = ul.cloneNode(true);appendDataToList(clone,data);ul.parentNode.replaceChild(clone,ul);
另外,我們經常在網頁上做一些動畫效果,對於這些動畫元素應使用絕對位置,因為絕對位置不存在於文檔流,所以它的改變不會影響到頁面的布局。
還有,如果大量元素使用了:hover會降低反應速度。此問題在ie8中更為顯著。