1. 程式人生 > >前端效能優化之重排和重繪

前端效能優化之重排和重繪

前言,最近利用碎片時間拜讀了一下尼古拉斯的另一鉅作《高效能JavaScript》,今天寫的文章從“老生常談”的頁面重繪和重排入手,去探究這兩個概念在頁面效能提升上的作用。

一.重排 & 重繪

有經驗的大佬對這個概念一定不會陌生,“瀏覽器輸入URL發生了什麼”。估計大家已經爛熟於心了,從計算機網路到JS引擎,一路飛奔到瀏覽器渲染引擎。 經驗越多就能理解的越深。感興趣的同學可以看一下這篇文章,深度和廣度俱佳 從輸入 URL 到頁面載入的過程?如何由一道題完善自己的前端知識體系!

切回正題,我們繼續探討何為重排。瀏覽器下載完頁面所有的資源後,就要開始構建DOM樹,於此同時還會構建渲染樹(Render Tree)。(其實在構建渲染樹之前,和DOM樹同期會構建Style Tree。DOM樹與Style Tree合併為渲染樹)

  • DOM樹 
    表示頁面的結構
  • 渲染樹 
    表示頁面的節點如何顯示

一旦渲染樹構建完成,就要開始繪製(paint)頁面元素了。當DOM的變化引發了元素幾何屬性的變化,比如改變元素的寬高,元素的位置,導致瀏覽器不得不重新計算元素的幾何屬性,並重新構建渲染樹,這個過程稱為“重排”。完成重排後,要將重新構建的渲染樹渲染到螢幕上,這個過程就是“重繪”。簡單的說,重排負責元素的幾何屬性更新,重繪負責元素的樣式更新。而且,重排必然帶來重繪,但是重繪未必帶來重排。比如,改變某個元素的背景,這個就不涉及元素的幾何屬性,所以只發生重排。

二. 重排觸發機制

上面已經提到了,重排發生的根本原理就是元素的幾何屬性發生了改變,那麼我們就從能夠改變元素幾何屬性的角度入手

  • 新增或刪除可見的DOM元素
  • 元素位置改變
  • 元素本身的尺寸發生改變
  • 內容改變
  • 頁面渲染器初始化
  • 瀏覽器視窗大小發生改變

三. 如何進行效能優化

重繪和重排的開銷是非常昂貴的,如果我們不停的在改變頁面的佈局,就會造成瀏覽器耗費大量的開銷在進行頁面的計算,這樣的話,我們頁面在使用者使用起來,就會出現明顯的卡頓。現在的瀏覽器其實已經對重排進行了優化,比如如下程式碼:

var div = document.querySelector('.div');
div.style.width = '200px';
div.style.background = 'red';
div.style.height = '300px';

比較久遠的瀏覽器,這段程式碼會觸發頁面2次重排,在分別設定寬高的時候,觸發2次,當代的瀏覽器對此進行了優化,這種思路類似於現在流行的MVVM框架使用的虛擬DOM,對改變的DOM節點進行依賴收集,確認沒有改變的節點,就進行一次更新。但是瀏覽器針對重排的優化雖然思路和虛擬DOM接近,但是還是有本質的區別。大多數瀏覽器通過佇列化修改並批量執行來優化重排過程。也就是說上面那段程式碼其實在現在的瀏覽器優化下,只構成一次重排。

但是還是有一些特殊的元素幾何屬性會造成這種優化失效。比如:

  • offsetTop, offsetLeft,...
  • scrollTop, scrollLeft, ...
  • clientTop, clientLeft, ...
  • getComputedStyle() (currentStyle in IE)

為什麼造成優化失效呢?仔細看這些屬性,都是需要實時回饋給使用者的幾何屬性或者是佈局屬性,當然不能再依靠瀏覽器的優化,因此瀏覽器不得不立即執行渲染佇列中的“待處理變化”,並隨之觸發重排返回正確的值。

接下來深入的介紹幾種效能優化的小TIPS

3.1 最小化重繪和重排

既然重排&重繪是會影響頁面的效能,尤其是糟糕的JS程式碼更會將重排帶來的效能問題放大。既然如此,我們首先想到的就是減少重排重繪。

3.1.1. 改變樣式

考慮下面這個例子:

// javascript
var el = document.querySelector('.el');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';

這個例子其實和上面那個例子是一回事兒,在最糟糕的情況下,會觸發瀏覽器三次重排。然鵝更高效的方式就是合併所有的改變一次處理。這樣就只會修改DOM節點一次,比如改為使用cssText屬性實現:

var el = document.querySelector('.el');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px';

沿著這個思路,聰明的老鐵一定就說了,你直接改個類名不也妥妥的。沒錯,還有一種減少重排的方法就是切換類名,而不是使用內聯樣式的cssText方法。使用切換類名就變成了這樣:

// css 
.active {
    padding: 5px;
    border-left: 1px;
    border-right: 2px;
}
// javascript
var el = document.querySelector('.el');
el.className = 'active';

3.1.2 批量修改DOM

3.1.3 快取佈局資訊

3.2讓元素脫離動畫流