簡體   English   中英

滾動事件:requestAnimationFrame VS requestIdleCallback VS 被動事件監聽器

[英]scroll events: requestAnimationFrame VS requestIdleCallback VS passive event listeners

正如我們所知,通常建議對滾動偵聽器進行去抖動,以便在用戶滾動時獲得更好的 UX。

但是,我經常發現像 Paul Lewis 這樣有影響力的人推薦使用requestAnimationFrame圖書館文章 然而,隨着網絡平台的快速發展,隨着時間的推移,一些建議可能會被棄用。

我看到的問題是處理滾動事件有非常不同的用例,比如構建視差網站,或處理無限滾動和分頁。

我看到 3 個主要工具可以在用戶體驗方面產生影響:

所以,我想知道,每個用例(我只有 2 個,但你可以想出其他的),我現在應該使用什么樣的工具來獲得非常好的滾動體驗?

更准確地說,我的主要問題將更多地與無限滾動視圖和分頁有關(通常不必觸發視覺動畫,但我們想要良好的滾動體驗),用requestIdleCallback + 的組合替換requestAnimationFrame是否更好被動滾動事件處理程序? 我還想知道什么時候使用requestIdleCallback來調用 API 或處理 API 響應以使滾動性能更好,或者瀏覽器可能已經為我們處理了什么?

雖然這個問題有點老,但我想回答它,因為我經常看到腳本,其中很多這些技術被濫用。

一般來說,您要求的所有工具( rAFrIC和被動偵聽器)都是很棒的工具,不會很快消失。 但是你必須知道為什么要使用它們。

在我開始之前:如果您生成滾動同步/滾動鏈接效果,如視差效果/粘性元素,使用rIC節流, setTimeout沒有意義,因為您想立即做出反應。

requestAnimationFrame

在瀏覽器想要計算文檔的新樣式和布局之前, rAF為您提供了框架生命周期內的點。 這就是為什么它非常適合用於動畫。 首先,它不會比瀏覽器計算布局(正確頻率)更頻繁或更少地被調用。 其次,在瀏覽器計算布局之前調用它(正確的時間)。 事實上,將rAF用於任何布局更改(DOM 或 CSSOM 更改)非常有意義。 rAFV-SYNC 同步,就像瀏覽器中的任何其他布局渲染相關內容一樣。

使用rAF進行節流/去抖動

Paul Lewis 的默認示例如下所示:

var scheduledAnimationFrame;
function readAndUpdatePage(){
  console.log('read and update');
  scheduledAnimationFrame = false;
}

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);

這種模式經常被使用/復制,盡管它在實踐中幾乎沒有意義。 (我問自己為什么沒有開發人員看到這個明顯的問題。)一般來說,理論上將所有內容限制到至少rAFrAF ,因為從瀏覽器請求布局更改更多是沒有意義的通常比瀏覽器呈現布局。

但是,每次瀏覽器呈現滾動位置更改時都會觸發scroll事件。 這意味着scroll事件與頁面的呈現同步。 從字面上看, rAF給你的東西是一樣的。 這意味着通過某些東西來限制某些東西沒有任何意義,每個定義已經被完全相同的東西限制了。

在實踐中,您可以通過添加一個console.log來檢查我剛剛說的內容,並檢查這種模式“阻止多個 rAF 回調”的頻率(答案是否定的,否則將是瀏覽器錯誤)。

  // Prevent multiple rAF callbacks.
  if (scheduledAnimationFrame){
    console.log('prevented rAF callback');
    return;
  }

正如您將看到的,這段代碼永遠不會被執行,它只是死代碼。

但是有一個非常相似的模式,因為不同的原因而有意義。 它看起來像這樣:

//declare box, element, pos
function writeLayout(){
    element.classList.add('is-foo');
}

window.addEventListener('scroll', ()=> {
    box = element.getBoundingClientRect();

    if(box.top > pos){
        requestAnimationFrame(writeLayout);
    }
});

使用這種模式,您可以成功減少甚至消除布局抖動。 想法很簡單:在滾動偵聽器中讀取布局並決定是否需要修改 DOM,然后調用使用 rAF 修改 DOM 的函數。 為什么這有幫助? rAF確保您移動布局失效(在幀的rAF )。 這意味着在同一框架內調用的任何其他代碼都適用於有效的布局,並且可以使用超快速的布局讀取方法進行操作。

這種模式實際上非常棒,我建議使用以下輔助方法(用 ES5 編寫):

/**
 * From https://stackoverflow.com/a/44779316
 *
 * @param {Function} fn Callback function
 * @param {Boolean|undefined} [throttle] Optionally throttle callback
 * @return {Function} Bound function
 *
 * @example
 * //generate rAFed function
 * jQuery.fn.addClassRaf = bindRaf(jQuery.fn.addClass);
 *
 * //use rAFed function
 * $('div').addClassRaf('is-stuck');
 */
function bindRaf(fn, throttle) {
  var isRunning;
  var that;
  var args;

  var run = function() {
    isRunning = false;
    fn.apply(that, args);
  };

  return function() {
    that = this;
    args = arguments;

    if (isRunning && throttle) {
      return;
    }

    isRunning = true;
    requestAnimationFrame(run);
  };
}

requestIdleCallback

來自類似於rAF的 API,但提供了完全不同的東西。 它為您提供了幀內的一些空閑時間。 (通常是在瀏覽器計算布局並完成繪制之后的時間點,但在垂直同步發生之前還有一些時間。)即使頁面從用戶視圖來看滯后,也可能有一些框架,瀏覽器所在的位置空轉。 雖然rIC可以給你最大。 50 毫秒。 大多數情況下,您只有 0.5 到 10 毫秒的時間來完成任務。 由於在框架生命周期中調用rIC回調的這一事實,您不應更改 DOM(為此使用rAF )。

最后,使用rIC對延遲rIC 、無限滾動等scroll偵聽器進行節流是rIC 對於這些類型的用戶界面,您甚至可以限制更多並在其前面添加setTimeout (所以你等待 100 毫秒,然后是rIC

去抖動節流的現場示例。)

這里還有一篇關於rAF的文章,其中包括兩個圖表,可能有助於理解“框架生命周期”內的不同點。

被動事件監聽器

發明了被動事件偵聽器以提高滾動性能。 現代瀏覽器將頁面滾動(滾動渲染)從主線程移動到合成線程。 (見https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/

但是有些事件會產生滾動,這可以通過腳本來阻止(發生在主線程中,因此可以恢復性能改進)。

這意味着一旦綁定了這些事件偵聽器之一,瀏覽器就必須等待這些偵聽器被執行,然后瀏覽器才能計算滾動。 這些事件主要是touchstarttouchmovetouchendwheel和理論在一定程度上keypresskeydown scroll事件本身不是這些事件之一。 scroll事件沒有默認動作,可以通過腳本阻止。

這意味着如果您不在touchstarttouchmovetouchend和/或wheel使用preventDefault ,請始終使用被動事件偵聽器,您應該沒問題。

如果您使用 preventDefault,請檢查您是否可以用 CSS touch-action屬性替換它或至少在您的 DOM 樹中降低它(例如,這些事件沒有事件委托)。 對於wheel偵聽器,您可以在mouseenter / mouseleave上綁定/取消綁定它們。

對於任何其他事件:使用被動事件偵聽器來提高性能是沒有意義的。 最重要的是要注意scroll事件不能被取消,因此是沒有意義使用被動事件偵聽scroll

在無限滾動視圖的情況下,您不需要touchmove ,您只需要scroll ,因此被動事件偵聽器甚至不適用。

恢復

回答你的問題

  • 對於延遲加載,無限視圖使用setTimeout + requestIdleCallback的組合作為您的事件偵聽器,並使用rAF進行任何布局寫入(DOM 突變)。
  • 對於即時效果仍然使用rAF進行任何布局寫入(DOM 突變)。

我還看到一些使用requestAnimationFrame的提及不僅要限制,還要完全替換滾動偵聽器。

有人可以對此發表評論嗎? 我認為這是不好的做法,因為即使不滾動它也會繼續循環。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM