Debounce Input Handlers
03 Sep 2018處理輸入的函式可能會造成效能問題,例如:阻礙幀(Frame)的繪製,導致畫面無法順利繪製;還有可能造成額外的版面配置(Recaculate Layout / Reflow)工作。
以下分三種情況說明
- 輸入的處理函式要避免長時間運作。
- 在輸入的處理函式中,要避免樣式的變更。
- 對輸入的處理函式做 Debounce(翻譯:防抖動 XD),在下一次的 requestAnimationFrame 的 callback 中儲存事件的資料和處理樣式的變更。
輸入的處理函式要避免長時間運作
在效能最佳的狀況下,當使用者與頁面互動時,頁面的合成器可以接受使用者的輕觸輸入並且移動內容,這些動作不會觸發渲染頁面的像素管道中的 JavaScript、樣式計算、版面配置、繪製,只會觸發合成階段。
圖片來源:Debounce Your Input Handlers
但若在 input 欄位加上 handler,例如:touchstart、touchmove 或 touchend,合成器的執行緒會等待 handler 執行完成,因為這個 handler 可能需要執行 preventDefault()
並且停止輕觸捲動的動作。即使沒有呼叫 preventDefault()
,handler 還是會等待,因此使用者就不能執行捲動的動作,而造成畫面無法順利繪製,看起來斷斷續續的樣子。
圖片來源:Debounce Your Input Handlers
由以上可知,應確保 handler 能快速執行完畢,合成器能繼續完成它的工作。
在輸入的處理函式中,要避免樣式的變更
這些處理函示(如上面所提到的 scroll 或 touch 的 handler),應該要在 requestAnimationFrame 的 callback 之前執行。
我們可能會在這些 handler 中執行樣式的變更,接著再執行 requestAnimationFrame,這時候就會產生「強制同步版面配置」(Forced Synchronous Layout,簡稱 FSL),這會是很大的效能問題,應儘量避免。
對輸入的處理函式做 Debounce
上面問題的解法就是對下一個 requestAnimationFrame 的 callback 來做視覺變更,除了可避免 FSL,還可保持程式碼的輕量,而不會封鎖使用者與頁面互動的捲動或輕觸事件了。
範例如下。
function onScroll(e) {
lastScrollY = window.scrollY; // store the scroll value for laterz.
if (scheduledAnimationFrame) {
// prevent multiple rAF callbacks.
return;
}
scheduledAnimationFrame = true;
requestAnimationFrame(readAndUpdatePage);
}
window.addEventListener('scroll', onScroll);