頁面的重繪與回流及優化


首先要清楚頁面呈現的具體過程:

1.  瀏覽器把獲取到的HTML代碼解析成1個DOM樹,HTML中的每個tag都是DOM樹中的1個節點,根節點就是我們常用的document對象。DOM樹里包含了所有HTML標簽,包括display:none隱藏,還有用JS動態添加的元素等。

2. 瀏覽器把所有樣式(用戶定義的CSS和用戶代理)解析成樣式結構體,在解析的過程中會去掉瀏覽器不能識別的樣式,比如IE會去掉-moz開頭的樣式,而FF會去掉_開頭的樣式。

3、DOM Tree 和樣式結構體組合后構建render tree, render tree類似於DOM tree,但區別很大,render tree能識別樣式,render tree中每個NODE都有自己的style,而且 render tree不包含隱藏的節點 (比如display:none的節點,還有head節點),因為這些節點不會用於呈現,而且不會影響呈現的,所以就不會包含到 render tree中。注意 visibility:hidden隱藏的元素還是會包含到 render tree中的,因為visibility:hidden 會影響布局(layout),會占有空間。根據CSS2的標准,render tree中的每個節點都稱為Box (Box dimensions),理解頁面元素為一個具有填充、邊距、邊框和位置的盒子。

4. 一旦render tree構建完畢后,瀏覽器就可以根據render tree來繪制頁面了。如下圖:

回流與重繪

1. 當render tree中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流(reflow)。每個頁面至少需要一次回流,就是在頁面第一次加載的時候。在回流的時候,瀏覽器會使渲染樹中受到影響的部分失效,並重新構造這部分渲染樹,完成回流后,瀏覽器會重新繪制受影響的部分到屏幕中,該過程成為重繪。

2. 當render tree中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響布局的,比如background-color。則就叫稱為重繪。

注意:回流必將引起重繪,而重繪不一定會引起回流。 我們需要明白,頁面若發生回流則需要付出很高的代價。

回流何時發生:

當頁面布局和幾何屬性改變時就需要回流。下述情況會發生瀏覽器回流:

1、添加或者刪除可見的DOM元素;

2、元素位置改變;

3、元素尺寸改變——邊距、填充、邊框、寬度和高度

4、內容改變——比如文本改變或者圖片大小改變而引起的計算值寬度和高度改變;

5、頁面渲染初始化;

6、瀏覽器窗口尺寸改變——resize事件發生時;

聰明的瀏覽器

從上個實例代碼中可以看到幾行簡單的JS代碼就引起了6次左右的回流、重繪。而且我們也知道回流的花銷也不小,如果每句JS操作都去回流重繪的話,瀏覽器可能就會受不了。所以很多瀏覽器都會優化這些操作,瀏覽器會維護1個隊列,把所有會引起回流、重繪的操作放入這個隊列,等隊列中的操作到了一定的數量或者到了一定的時間間隔,瀏覽器就會flush隊列,進行一個批處理。這樣就會讓多次的回流、重繪變成一次回流重繪。

雖然有了瀏覽器的優化,但有時候我們寫的一些代碼可能會強制瀏覽器提前flush隊列,這樣瀏覽器的優化可能就起不到作用了。當你請求向瀏覽器請求一些 style信息的時候,就會讓瀏覽器flush隊列,比如:

1. offsetTop, offsetLeft, offsetWidth, offsetHeight

2. scrollTop/Left/Width/Height

3. clientTop/Left/Width/Height

4. width,height

5. 請求了getComputedStyle(), 或者 IE的 currentStyle     // 這個屬性表示經過計算過最終的樣式,可以參考張鑫旭的博客

當你請求上面的一些屬性的時候,瀏覽器為了給你最精確的值,需要flush隊列,因為隊列中可能會有影響到這些值的操作。即使你獲取元素的布局和樣式信息跟最近發生或改變的布局信息無關,瀏覽器都會強行刷新渲染隊列。

如何減少回流、重繪

減少回流、重繪其實就是需要減少對render tree的操作(合並多次多DOM和樣式的修改),並減少對一些style信息的請求,盡量利用好瀏覽器的優化策略。具體方法有:

1. 直接改變className,如果動態改變樣式,則使用cssText(考慮沒有優化的瀏覽器)

// 不好的寫法
var left = 1;
var top = 1;
el.style.left = left + "px";
el.style.top = top + "px";
// 比較好的寫法
el.className += " className1";
 
// 比較好的寫法
el.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";

 

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

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

//不好的寫法(模式中所說的反模式)
var p, t;
p = document.creatElement('p');
t = document.creatTextNode('fist paragraph');
p.appendChild(t);
document.body.appendChild(p);  //將引起一次回流

p = document.creatElement('p');
t = document.creatTextNode('second paragraph');
p.appendChild(t);
document.body.appendChild(p);  //將再引起一次回流

//好的寫法
var p, t, frag;
frag = document.creatDocumentFragment();
p = document.creatElement('p');
t = document.creatTextNode('fist paragraph');
p.appendChild(t);
farg.appendChild(p);

p = document.creatElement('p');
t = document.creatTextNode('second paragraph');
p.appendChild(t);
farg.appendChild(p);

document.body.appendChild(frag);    //相比前面的方法,這里僅僅引起一次回流,倘若頁面里有很多這樣的操作,利用文檔隨便將會提升很多

b) 使用display:none技術,只引發兩次回流和重繪; ( 只是減少重繪和回流的次數,display:none 是會引起重繪並回流,相對來說,visibility: hidden只會引起重繪 )


c) 使用cloneNode(true or false) 和 replaceChild 技術,引發一次回流和重繪;

//建立克隆鏡像
var oldNode = document.getElementById('target'),
      clone = oldNode.cloneNode(true);   //深復制


//   處理克隆對象的操作....

//完成后
oldNode.parentNode.replaceChild(clone,oldNode);

 3.不要經常訪問會引起瀏覽器flush隊列的屬性,如果你確實要訪問,利用緩存

//BAD WAY
for(循環) {
el.style.left = el.offsetLeft + 5 + "px";
el.style.top = el.offsetTop + 5 + "px";
}
 
// 這樣寫好點
var left = el.offsetLeft,
top = el.offsetTop,
s = el.style; 
for (循環) { 
left += 10; 
top += 10; 
s.left = left + "px"; 
s.top = top + "px"; 
}

4. 讓元素脫離動畫流,減少回流的Render Tree的規模

 

本文有參考自http://www.css88.com/archives/4996,也有自己的一些理解,若有不對歡迎拍磚


免責聲明!

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



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