DOM 操作最佳化
首先澄清兩個概念——Repaint 和 Reflow:Repaint 也叫 Redraw,它指的是一種不會影響當前 DOM 的結構和布局的一種重繪動作。如下動作會產生 Repaint 動作:
不可見到可見(visibility 樣式屬性);
顏色或圖片變化(background, border-color, color 樣式屬性);
不改變頁面元素大小,形狀和位置,但改變其外觀的變化
Reflow 比起 Repaint 來講就是一種更加顯著的變化了。它主要發生在 DOM 樹被操作的時候,任何改變 DOM 的結構和布局都會產生 Reflow。但一個元素的 Reflow 操作發生時,它的所有父元素和子項目都會放生 Reflow,最後 Reflow 必然會導致 Repaint 的產生。舉例說明,如下動作會產生 Reflow 動作:
瀏覽器視窗的變化;
DOM 節點的添加刪除操作
一些改變頁面元素大小,形狀和位置的操作的觸發 通過 Reflow 和 Repaint 的介紹可知,每次 Reflow 比其 Repaint 會帶來更多的資源消耗,因此,我們應該盡量減少 Reflow 的發生,或者將其轉化為只會觸發 Repaint 操作的代碼。
| 代碼如下 |
複製代碼 |
var tipBox = document.createElement('div'); document.body.appendChild('tipBox');//reflow var tip1 = document.createElement('div'); var tip2 = document.createElement('div'); tipBox.appendChild(tip1);//reflow tipBox.appendChild(tip2);//reflow
|
如上的代碼,會產生三次reflow,最佳化後的代碼如下:
| 代碼如下 |
複製代碼 |
var tipBox = document.createElement('div'); tip1 = document.createElement('div'); tip2 = document.createElement('div'); tipBox.appendChild(tip1); tipBox.appendChild(tip2); document.body.appendChild('tipBox');//reflow
|
當然還可以利用 display 來減少reflow次數
| 代碼如下 |
複製代碼 |
var tipBox = document.getElementById('tipBox'); tipBox.style.display = 'none';//reflow tipBox.appendChild(tip1); tipBox.appendChild(tip2); tipBox.appendChild(tip3); tipBox.appendChild(tip4); tipBox.appendChild(tip5); tipBox.style.width = 120; tipBox.style.height = 60; tipBox.style.display = 'block';//reflow
|
DOM元素測量屬性和方法也會觸發reflow,如下:
| 代碼如下 |
複製代碼 |
var tipWidth = tipBox.offsetWidth;//reflow tipScrollLeft = tipBox.scrollLeft;//reflow display = window.getComputedStyle(div,'').getPropertyValue('display');//reflow
|
觸發reflow的屬性和方法大概有這些:
| 代碼如下 |
複製代碼 |
offsetLeft offsetTop offsetHeight offsetWidth scrollTop/Left/Width/Height clientTop/Left/Width/Height getComputedStyle() currentStyle(in IE))
|
我們可以用臨時變數將“offsetWidth”的值緩衝起來,這樣就不用每次訪問“offsetWidth”屬性。這種方式在迴圈裡面非常適用,可以極大地提高效能。
如果有批量的樣式屬性需要修改,建議通過替換className的方式來降低reflow的次數,曾經有這樣一個情境:有三個intput,分別對應下面三個圖片和三個內容地區,第二input選中的時候,第二圖片顯示,其他圖片隱藏,第二塊內容顯示,其他內容隱藏,直接操作DOM節點的代碼如下
| 代碼如下 |
複製代碼 |
var input = []; pics = []; contents = []; ...... inputFrame.onclick =function(e){ var _e,_target; _e = e ? window.event : null; if(!_e){ return; }else{ _target = _e.srcElement || _e.target ; _index = getIndex(_target);//reflow兩次 show(_target,_index);//reflow兩次 } } function show(target,j){ for(var i = 0,i<3;i++){ target[i].style.display = 'none';//reflow } target[j].style.display = 'block';//reflow } function getIndex(targer){ if(target){ .....//擷取當前的元素索引 return index; } } |
如果是通過css預先定義元素的隱藏和顯示,通過對父級的className進行操縱,將會把reflow的次數減少到1次
| 代碼如下 |
複製代碼 |
.pbox .pic,.pbox content{display:none} .J_pbox_0 .pic0,.J_pbox_0 .content0{diplay:block} .J_pbox_1 .pic1,.J_pbox_1 .content1{diplay:block} .J_pbox_2 .pic2,.J_pbox_2 .content2{diplay:block} var input = [], parentBox = document.getELementById('J_Pbox'); ...... inputFrame.onclick =function(e){ var _e,_target; if(){ ... }else{ ... parentBox.className = 'pbox J_pbox_'+_infex;//reflow一次 } }
|