對用戶輸入事件的處理去抖動
用戶輸入事件處理函數是一個可能會導致web應用性能問題的因素,因為它們在運行時會阻塞幀的渲染,并且會導致額外且不必要的布局的發生。
TL;DR
- 避免使用運行時間過長的輸入事件處理函數,它們會阻塞頁面的滾動
- 避免在輸入事件處理函數中修改樣式屬性
- 對輸入事件處理函數去抖動,存儲事件對象的值,然后在requestAnimationFrame回調函數中修改樣式屬性
避免使用運行時間過長的輸入事件處理函數
在理想情況下,當用戶在設備屏幕上觸摸了頁面上某個位置時,頁面的渲染層合并線程將接收到這個觸摸事件并作出響應,比如移動頁面元素。這個響應過程是不需要瀏覽器主線程的參與的,也就是說,不會導致JavaScript、布局和繪制過程的發生。
但是,如果你對這個被觸摸的元素綁定了輸入事件處理函數,比如touchstart
、touchmove
或者touchend
,那么渲染層合并線程必須等待這些被綁定的處理函數的執行完畢之后才能被執行。因為你可能在這些處理函數中調用了類似preventDefault()
的函數,這將會阻止輸入事件(touch/scroll等)的默認處理函數的運行。事實上,即便你沒有在事件處理函數中調用preventDefault()
,渲染層合并線程也依然會等待,也就是用戶的滾動頁面操作被阻塞了,表現出的行為就是滾動出現延遲或者卡頓(幀丟失)。
簡而言之,你必須確保對用戶輸入事件綁定的任何處理函數都能夠快速執行完畢,以便騰出時間來讓渲染層合并線程來完成它的工作。
避免在輸入事件處理函數中修改樣式屬性
輸入事件處理函數,比如scroll/touch事件的處理,都會在requestAnimationFrame
之前被調用執行。
因此,如果你在上述輸入事件的處理函數中做了修改樣式屬性的操作,那么這些操作會被瀏覽器暫存起來。然后在調用requestAnimationFrame
的時候,如果你在一開始做了讀取樣式屬性的操作,那么根據“避免大規模、復雜的布局”中所述,你將會觸發瀏覽器的強制同步布局過程!
對滾動事件處理函數去抖動
有一個方法能同時解決上面的兩個問題:對樣式修改操作去抖動,控制其僅在下一次requestAnimationFrame
中發生:
function onScroll (evt) {
// Store the scroll value for laterz.
lastScrollY = window.scrollY;
// Prevent multiple rAF callbacks.
if (scheduledAnimationFrame)
return;
scheduledAnimationFrame = true;
requestAnimationFrame(readAndUpdatePage);
}
window.addEventListener('scroll', onScroll);
這么做還有一個額外的好處,就是能使你的事件處理函數變得輕量。這很關鍵,因為它能使包含復雜計算代碼的頁面也能快速響應scroll/touch事件!
來源:https://developers.google.com/web/fundamentals/performance/rendering/debounce-your-input-handlers?hl=zh-cn