歡迎光臨
每天分享高質量文章

Web效能最佳化系列:預防佈局抖動

預防“佈局抖動”

佈局抖動是因 JavaScript 的 DOM 元素被多被次暴力寫,然後讀,導致檔案重排而出現的。

// 讀

var h1 = element1.clientHeight;

// 寫(無效佈局)

element1.style.height = (h1 * 2) + ‘px’;

// 讀(觸釋出局)

var h2 = element2.clientHeight;

// 寫(無效佈局)

element2.style.height = (h2 * 2) + ‘px’;

// 讀(觸釋出局)

var h3 = element3.clientHeight;

// 寫(無效佈局)

element3.style.height = (h3 * 2) + ‘px’;

當DOM元素被寫入值,佈局就“無效”,而多次這樣就會導致檔案重排。瀏覽器很懶,它總想等到當前操作(或幀)的最後一步才重排。

然而,如果在當前操作(幀)完成前,從DOM元素中獲取值,這會迫使瀏覽器提早執行佈局操作,這稱為“強制同步佈局”,這可是效能殺手!

佈局抖動的副作用在現代桌面瀏覽器上並不明顯;但對於低配置的移動裝置來說,其後果就不堪設想了。

能快速修複?

在理想情況下,我們可能透過簡單地重覆執行,以至於將DOM元素的讀寫操作放在一起執行。這意味著檔案只需重排一次即可。

// 讀

var h1 = element1.clientHeight;

var h2 = element2.clientHeight;

var h3 = element3.clientHeight;

// 寫(無效佈局)

element1.style.height = (h1 * 2) + ‘px’;

element2.style.height = (h2 * 2) + ‘px’;

element3.style.height = (h3 * 2) + ‘px’;

// 檔案在最後一幀將進行重排

現實情況會怎麼樣?

現實情況並非如此簡單。大型應用程式的程式碼會分散到各個地方,因此這些地方都有危險的DOM操作。所以不能簡單地(絕對不應該)聚集它們,而需要解耦程式碼,只是需要控制好執行順序。那如何讓讀寫操作捆綁在一起,從而獲得最佳效能呢?

進入requestAnimationFrame

window.requestAnimationFrame是一個將操作安排在下一幀一起執行的函式,類似於setTimeout(fn, 0)。這是非常有用的,因為能使用它來安排所有DOM的寫操作在下一幀一起執行,保留所有DOM的讀操作在當前同步狀態。

// 讀

var h1 = element1.clientHeight;

// 寫

requestAnimationFrame(function() {

element1.style.height = (h1 * 2) + ‘px’;

});

// 讀

var h2 = element2.clientHeight;

// 寫

requestAnimationFrame(function() {

element2.style.height = (h2 * 2) + ‘px’;

});

這意味著我們能很好地封裝程式碼了。經過小小調整後的程式碼,就將高耗能的DOM操作捆綁在一起!實在太棒了!

工作實體

我建立了一個工作案例來證明這個觀點。從第一個截圖的chrome時間軸可看出,有多個佈局抖動穿插其中。

在改用requestAnimationFrame 後,僅僅只觸發一次佈局事件,其結果是操作快了約96%。

它具有伸縮性嗎?

在一個簡單案例裡,使用requestAnimationFrame來延遲DOM寫操作,從而大大提高效能,但這項技術沒有伸縮性可言。

在我們的應用中,可能需要在DOM元素上執行先寫後讀操作,然後再次掉入佈局抖動的坑,只是在不同幀。

// 讀

var h1 = element1.clientHeight;

// 寫

requestAnimationFrame(function() {

element1.style.height = (h1 * 2) + ‘px’;

// 我們可能想在設定高度後再讀取新高度值。

var height = element1.clientHeight;

});

我們可以將讀操作放到另外一個requestAnimationFrame ,但我們不能保證應用程式的另一部分,沒有把寫操作放在同一幀上。

介紹 ‘FastDom’

FastDom是一個輕量的庫,它提供一個公共介面,能讓DOM的讀/寫操作捆綁在一起。其實,它就是利用上述同樣的 requestAnimationFrame 技術來大大提高DOM操作速度。

fastdom.read(function() {

var h1 = element1.clientHeight;

fastdom.write(function() {

element1.style.height = (h1 * 2) + ‘px’;

});

});

fastdom.read(function() {

var h2 = element2.clientHeight;

fastdom.write(function() {

element2.style.height = (h1 * 2) + ‘px’;

});

});

FastDom透過接收讀寫操作,併在下一幀捆綁它們(先讀後寫),從而消除DOM的相互影響。這意味著我們能獨立編寫應用程式元件,而不用擔心它們在應用程式中互相影響。

使用FastDom的啟示

透過使用FastDom,會讓所有DOM任務變成非同步,這意味著你不能總是假設DOM將會以什麼狀態進行操作。操作從之前的同步,變成現在的非同步方式。因此,可能沒執行完非同步處理函式就會執行下一步操作了。

要解決這一點,我打算用事件系統來明確操作何時完成,和明確依賴於完成後所做出的響應操作。

雖然所做工作是一樣的,但能透過增加程式碼量來顯著提高效能。我個人認為這個代價小。

FastDom案例

  • Animation example:http://wilsonpage.github.io/fastdom/examples/animation.html
  • Aspect ratio example:http://wilsonpage.github.io/fastdom/examples/aspect-ratio.html

完善FastDom

web應用缺少一個明確的方式,來解決佈局抖動問題。正如一個應用程式很難協調所有不同的部分,來確保產品最終是高效的。如果FastDom能為開發者們提供一個簡單介面來解決這個問題,那隻能意味著它是個好東西。

瞧一瞧 FastDom 專案,歡迎隨時透過 pull requests 或 filing issues 來完善它。

原文出處:wilsonpage.co.uk

譯文出處:伯樂線上 – 劉健超-J.c

連結:http://web.jobbole.com/82546/

贊(0)

分享創造快樂