重排和重繪
網頁生成過程:
- HTML被HTML解析器解析成DOM 樹
- css則被css解析器解析成CSSOM 樹
- 結合DOM樹和CSSOM樹,生成一棵渲染樹(Render Tree)
- 生成佈局(flow),即將所有渲染樹的所有節點進行平面合成
- 將佈局繪製(paint)在屏幕上
第四步和第五步是最耗時的部分,這兩步合起來,就是我們通常所說的渲染。(之前有博客詳細解釋了)
網頁生成的時候,至少會渲染一次。
在用戶訪問的過程中,還會不斷重新渲染
重新渲染需要重複之前的第四步(重新生成佈局)+第五步(重新繪製)或者只有第五個步(重新繪製)。
定義
-
重排是什麼:重新生成佈局。當DOM 的變化影響了元素的幾何屬性(寬和高)--比如改變邊框寬度或給段落增加文字導致行數增加--瀏覽器需要重新計算元素的幾何屬性,同樣其他元素的幾何屬性和位置也會因此受到影響。瀏覽器會使渲染樹中受到影響的部分失效,並重新構造渲染樹。這個過程稱爲重排。
-
重繪是什麼:重新繪製。完成重排後,瀏覽器會重新繪製受影響的部分到屏幕中。這個過程稱爲重繪。
發生重排的情況
- 添加或刪除可見的DOM元素
- 元素位置改變
- 元素本身的尺寸發生改變
- 內容改變
- 頁面渲染器初始化
- 瀏覽器窗口大小發生改變
重排與重繪的關係
重排一定會導致重繪,重繪不一定導致重排。如果DOM變化不影響幾何屬性,元素的佈局沒有改變,則只發生一次重繪(不需要重排)。
對性能的影響
重排和重繪會不斷觸發,這是不可避免的。但是,它們非常耗費資源,是導致網頁性能低下的根本原因。
提高網頁性能,就是要降低"重排"和"重繪"的頻率和成本,儘量少觸發重新渲染。
渲染樹變化的排隊
前面提到,DOM變動和樣式變動,都會觸發重新渲染。但是,瀏覽器已經很智能了,會盡量把所有的變動集中在一起,排成一個隊列,然後一次性執行,儘量避免多次重新渲染。
例子:
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
根據我們上文的定義,這段代碼理論上會觸發4次重排+重繪,因爲每一次都改變了元素的幾何屬性,實際上最後只觸發了一次重排,我們修改了元素的幾何屬性,導致瀏覽器觸發重排或重繪時。它會把該操作放進渲染隊列,等到隊列中的操作到了一定的數量或者到了一定的時間間隔時,瀏覽器就會批量執行這些操作。
強制刷新隊列:
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
這段代碼會觸發4次重排+重繪,因爲在console
中你請求的這幾個樣式信息,無論何時瀏覽器都會立即執行渲染隊列的任務,即使該值與你操作中修改的值沒關聯。
因爲隊列中,可能會有影響到這些值的操作,爲了給我們最精確的值,瀏覽器會立即重排+重繪。
強制刷新隊列的style樣式請求:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeight
- getComputedStyle(), 或者 IE的 currentStyle
我們在開發中,應該謹慎的使用這些style請求,注意上下文關係,避免一行代碼一個重排,這對性能是個巨大的消耗!!!
性能優化:
1. 分離讀寫操作
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
發生一次重排,原因上面有寫,所有的讀操作放在寫操作之後
2. 樣式集中改變
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
雖然現在大部分瀏覽器有渲染隊列優化,不排除有些瀏覽器以及老版本的瀏覽器效率仍然低下:
建議通過改變class或者csstext屬性集中改變樣式,這種辦法可維護性好,還可以幫助我們免除顯示性代碼
有很小的性能影響,改變class需要檢查級聯樣式,但是符合BEM標準可以更好的解耦,增加可維護性,建議這樣寫
//例
// 獲取el類型
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
3. 緩存佈局信息
// 強制刷新 觸發兩次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
//緩存佈局信息 讀寫分離
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
4. 離線改變dom
隱藏要操作的dom
在要操作dom之前,通過display隱藏dom,當操作完成之後,纔將元素的display屬性爲可見,因爲不可見的元素不會觸發重排和重繪。
通過使用DocumentFragment創建一個dom
碎片,在它上面批量操作dom,操作完成之後,再添加到文檔中,這樣只會觸發一次重排。
5. position屬性爲absolute或fixed
position屬性爲absolute或fixed的元素,重排開銷比較小,不用考慮它對其他元素的影響
6. 優化動畫
可以把動畫效果應用到position屬性爲absolute或fixed的元素上,這樣對其他元素影響較小