Debounce Input Handlers

處理輸入的函式可能會造成效能問題,例如:阻礙幀(Frame)的繪製,導致畫面無法順利繪製;還有可能造成額外的版面配置(Recaculate Layout / Reflow)工作。

以下分三種情況說明

輸入的處理函式要避免長時間運作

在效能最佳的狀況下,當使用者與頁面互動時,頁面的合成器可以接受使用者的輕觸輸入並且移動內容,這些動作不會觸發渲染頁面的像素管道中的 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);

References


效能調校 關鍵轉譯路徑 requestAnimationFrame 轉譯效能 Rendering Performance Critical Rendering Path 前端效能 系列文