DOM樹和渲染樹
當瀏覽器下載完所有頁面HTML 標記,JavaScript,CSS,圖片之后,它解析文件并創建兩個內部數據結構:一棵DOM樹表示頁面結構,一棵渲染樹表示DOM節點如何顯示。
渲染樹中為每個需要顯示的DOM 樹節點存放至少一個節點(隱藏DOM 元素在渲染樹中沒有對應節點)。渲染樹上的節點稱為“框”或者“盒”,符合CSS 模型的定義,將頁面元素看作一個具有填充、邊距、邊框和位置的盒。一旦DOM 樹和渲染樹構造完畢,瀏覽器就可以顯示(繪制)頁面上的元素了。
重排版
當DOM 改變影響到元素的幾何屬性(寬和高)——例如改變了邊框寬度或在段落中添加文字,將發生一系列后續動作——瀏覽器需要重新計算元素的幾何屬性,而且其他元素的幾何屬性和位置也會因此改變受到影響。瀏覽器使渲染樹上受到影響的部分失效,然后重構渲染樹。這個過程被稱作重排版。重排版完成時,瀏覽器在一個重繪進程中重新繪制屏幕上受影響的部分。
不是所有的DOM 改變都會影響幾何屬性。例如,改變一個元素的背景顏色不會影響它的寬度或高度。在這種情況下,只需要重繪(不需要重排版),因為元素的布局沒有改變。
重繪和重排版是負擔很重的操作,可能導致網頁應用的用戶界面失去相應。所以,十分有必要盡可能減少這類事情的發生。
發生重排版情況
正如前面所提到的,當布局和幾何改變時需要重排版。在下述情況中會發生重排版:
(1)添加或刪除可見的DOM 元素
(2)元素位置改變
(3)元素尺寸改變(因為邊距,填充,邊框寬度,寬度,高度等屬性改變)
(4)內容改變,例如,文本改變或圖片被另一個不同尺寸的所替代最初的頁面渲染
(5)瀏覽器窗口改變尺寸
根據改變的性質,渲染樹上或大或小的一部分需要重新計算。某些改變可導致重排版整個頁面:例如,當一個滾動條出現時。因為計算量與每次重排版有關,大多數瀏覽器通過隊列化修改和批量顯示優化重排版過程。然而,你可能(經常不由自主地)強迫隊列刷新并要求所有計劃改變的部分立刻應用。獲取布局信息的操作將導致刷新隊列動作,這意味著使用了下面這些方法:
offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle() (currentStyle in IE)(在IE 中此函數稱為currentStyle)
布局信息由這些屬性和方法返回最新的數據,所以瀏覽器不得不運行渲染隊列中待改變的項目并重新排版以返回正確的值。
在改變風格的過程中,最好不要使用前面列出的那些屬性。任何一個訪問都將刷新渲染隊列,即使你正在獲取那些最近未發生改變的或者與最新的改變無關的布局信息。
重拍版實例分析
1)考慮下面這個例子,它改變同一個風格屬性三次(這也許不是你在真正的代碼中所見到的,不過它孤立地展示出一個重要話題):
// setting and retrieving styles in succession var computed, tmp = '', bodystyle = document.body.style; if (document.body.currentStyle) { // IE, Opera computed = document.body.currentStyle; } else { // W3C computed = document.defaultView.getComputedStyle(document.body, ''); } // inefficient way of modifying the same property // and retrieving style information right after bodystyle.color = 'red'; tmp = computed.backgroundColor; bodystyle.color = 'white'; tmp = computed.backgroundImage; bodystyle.color = 'green'; tmp = computed.backgroundAttachment;
在這個例子中,body 元素的前景色被改變了三次,每次改變之后,都導入computed 的風格。導入的屬性backgroundColor, backgroundImage, 和backgroundAttachment 與顏色改變無關。然而,瀏覽器需要刷新渲染隊列并重排版,因為computed 的風格被查詢而引發。
比這個不講效率的例子更好的方法是不要在布局信息改變時查詢它。如果將查詢computed 風格的代碼搬到末尾,代碼看起來將是這個樣子:
bodystyle.color = 'red'; bodystyle.color = 'white'; bodystyle.color = 'green'; tmp = computed.backgroundColor; tmp = computed.backgroundImage; tmp = computed.backgroundAttachment;
在所有瀏覽器上,第二個例子將更快,重排版和重繪代價昂貴,所以,提高程序響應速度一個好策略是減少此類操作發生的機會。為減少發生次數,你應該將多個DOM 和風格改變合并到一個批次中一次性執行。
2)考慮這個例子:
var el = document.getElementById('mydiv'); el.style.borderLeft = '1px'; el.style.borderRight = '2px'; el.style.padding = '5px';
這里改變了三個風格屬性,每次改變都影響到元素的幾何屬性。在這個糟糕的例子中,它導致瀏覽器重排版了三次。大多數現代瀏覽器優化了這種情況只進行一次重排版,但是在老式瀏覽器中,或者同時有一個分離的同步進程(例如使用了一個定時器),效率將十分低下。如果其他代碼在這段代碼運行時查詢布局信息,將導致三次重布局發生。而且,此代碼訪問DOM 四次,可以被優化。
一個達到同樣效果而效率更高的方法是:將所有改變合并在一起執行,只修改DOM 一次。可通過使用
cssText 屬性實現:
var el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
這個例子中的代碼修改cssText 屬性,覆蓋已存在的風格信息。如果你打算保持當前的風格,你可以將它附加在cssText 字符串的后面。
el.style.cssText += '; border-left: 1px;';
另一個一次性改變風格的辦法是修改CSS 的類名稱,而不是修改內聯風格代碼。這種方法適用于那些風格不依賴于運行邏輯,不需要計算的情況。改變CSS 類名稱更清晰,更易于維護;它有助于保持腳本免除顯示代碼,雖然它可能帶來輕微的性能沖擊,因為改變類時需要檢查級聯表。
var el = document.getElementById('mydiv');
el.className = 'active';
批量修改DOM如何減少重排版次數
當你需要對DOM 元素進行多次修改時,你可以通過以下步驟減少重繪和重排版的次數:
1.從文檔流中摘除該元素
2.對其應用多重改變
3.將元素帶回文檔中
此過程引發兩次重排版——第一步引發一次,第三步引發一次。如果你忽略了這兩個步驟,那么第二步
中每次改變都將引發一次重排版。
有三種基本方法可以將DOM 從文檔中摘除:
1) 隱藏元素,進行修改,然后再顯示它。
2) 使用一個文檔片斷在已存DOM 之外創建一個子樹,然后將它拷貝到文檔中。
3) 將原始元素拷貝到一個脫離文檔的節點中,修改副本,然后覆蓋原始元素。
為演示脫離文檔操作,考慮這樣一個鏈接列表,它必須被更多的信息所更新:
<ul id="mylist"> <li><a href="http://phpied.com">Stoyan</a></li> <li><a href="http://julienlecomte.com">Julien</a></li> </ul>
假設附加數據已經存儲在一個對象中了,需要插入到這個列表中。這些數據定義如下:
var data = [ { "name": "Nicholas", "url": "http://nczonline.net" }, { "name": "Ross", "url": "http://techfoolery.com" } ];
下面是一個通用的函數,用于將新數據更新到指定節點中:
function appendDataToElement(appendToElement, data) { var a, li; for (var i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li = document.createElement('li'); li.appendChild(a); appendToElement.appendChild(li); } };
將數據更新到列表而不管重排版問題,最明顯的方法如下:
var ul = document.getElementById('mylist');
appendDataToElement(ul, data);
使用這個方法,然而,data 隊列上的每個新條目追加到DOM 樹都會導致重排版。
如前面所討論過的,減少重排版的一個方法是通過改變display 屬性,臨時從文檔上移除<ul>元素然后再恢復它。
var ul = document.getElementById('mylist'); ul.style.display = 'none'; appendDataToElement(ul, data); ul.style.display = 'block';
另一種減少重排版次數的方法是:在文檔之外創建并更新一個文檔片斷,然后將它附加在原始列表上。文檔片斷是一個輕量級的document 對象,它被設計專用于更新、移動節點之類的任務。文檔片斷一個便利的語法特性是當你向節點附加一個片斷時,實際添加的是文檔片斷的子節點群,而不是片斷自己。下面的例子減少一行代碼,只引發一次重排版,只觸發“存在DOM”一次。
var fragment = document.createDocumentFragment(); appendDataToElement(fragment, data); document.getElementById('mylist').appendChild(fragment);
第三種解決方法首先創建要更新節點的副本,然后在副本上操作,最后用新節點覆蓋老節點:
var old = document.getElementById('mylist'); var clone = old.cloneNode(true); appendDataToElement(clone, data); old.parentNode.replaceChild(clone, old);
推薦盡可能使用文檔片斷(第二種解決方案)因為它涉及最少數量的DOM 操作和重排版。唯一潛在的缺點是,當前文檔片斷還沒有得到充分利用,開發者可能不熟悉此技術。
瀏覽器通過隊列化修改和批量運行的方法,盡量減少重排版次數。當你查詢布局信息如偏移量、滾動條位置,或風格屬性時,瀏覽器刷隊列并執行所有修改操作,以返回最新的數值。最好是盡量減少對布局信息的查詢次數,查詢時將它賦給局部變量,并用局部變量參與計算。
考慮一個例子,將元素myElement 向右下方向平移,每次一個像素,起始于100×100 位置,結束于500×500位置,在timeout 循環體中你可以使用:
// inefficient myElement.style.left = 1 + myElement.offsetLeft + 'px'; myElement.style.top = 1 + myElement.offsetTop + 'px'; if (myElement.offsetLeft >= 500) { stopAnimation(); }
這樣做很沒效率,因為每次元素移動,代碼查詢偏移量,導致瀏覽器刷新渲染隊列,并沒有從優化中獲益。另一個辦法只需要獲得起始位置值一次,將它存入局部變量中var current = myElement.offsetLeft;。然后,在動畫循環中,使用current 變量而不再查詢偏移量:
current++ myElement.style.left = current + 'px'; myElement.style.top = current + 'px'; if (current >= 500) { stopAnimation(); }
轉載自:http://exception.thinksaas.cn/0/16/16591.html
文章列表
留言列表