web-dev-qa-db-ja.com

スクロールイベント:requestAnimationFrame VS requestIdleCallback VSパッシブイベントリスナー

ご存知のように、ユーザーがスクロールしているときにUXがより良くなるように、スクロールリスナーをデバウンスすることをお勧めします。

ただし、Paul Lewisのような影響力のある人がrequestAnimationFrameの使用を推奨している場合、 ライブラリ および 記事 をよく見つけました。ただし、Webプラットフォームが急速に進歩するにつれて、時間の経過とともに一部のアドバイスが非推奨になる可能性があります。

私が見る問題は、視差ウェブサイトの構築、無限スクロールとページネーションの処理など、スクロールイベントを処理するための非常に異なるユースケースがあることです。

UXの用語に違いをもたらすことができる3つの主要なツールがあります。

だから、ユースケースごとに(私は2つしか持っていませんが、他のものを思い付くことができます)、非常に良いスクロール体験をするために今どんな種類のツールを使用する必要がありますか?

より正確には、私の主な質問は、無限スクロールビューとページネーション(通常は視覚的なアニメーションをトリガーする必要はありませんが、優れたスクロールエクスペリエンスが必要です)に関連します。requestAnimationFramerequestIdleCallback +パッシブスクロールイベントハンドラのコンボ?また、APIの呼び出しやAPI応答の処理にrequestIdleCallbackを使用してスクロールのパフォーマンスを向上させるのが理にかなっているのか、それともブラウザが既に処理しているのでしょうか。

38

この質問は少し古いですが、これらのテクニックの多くが誤用されているスクリプトをよく見かけるので、答えたいと思います。

一般的に、すべての質問されたツール(rAFrIC、およびパッシブリスナー)は優れたツールであり、すぐに消えることはありません。しかし、それらを使用する理由を知っておく必要があります。

始める前に:パララックスエフェクト/スティッキーエレメ​​ントのようなスクロール同期/スクロールリンクエフェクトを生成する場合、rICsetTimeoutを使用したスロットルは、すぐに反応したいので意味がありません。

requestAnimationFrame

rAFは、ブラウザがドキュメントの新しいスタイルとレイアウトを計算する直前のフレームライフサイクル内のポイントを提供します。これが、アニメーションに使用するのに最適な理由です。最初に、ブラウザがレイアウトを計算するよりも頻繁に呼び出されることはありません(正しい頻度)。次に、ブラウザがレイアウトを計算する直前に呼び出されます(正しいタイミング)。実際、レイアウトの変更(DOMまたはCSSOMの変更)にrAFを使用することは非常に理にかなっています。 rAFは、ブラウザで関連するものをレンダリングする他のレイアウトと同様に、V-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);

このパターンは非常に頻繁に使用/コピーされますが、実際にはほとんど意味がありません。 (そして、この明らかな問題を開発者が見ない理由を私は自問しています。)一般的に、少なくともrAFにすべてを絞るのは理論的には非常に理にかなっています。多くの場合、ブラウザがレイアウトをレンダリングするよりも。

ただし、ブラウザが renders スクロール位置を変更するたびに、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は、レイアウトの無効化を(フレームの最後に)移動するようにします。これは、同じフレーム内で呼び出される他のコードが有効なレイアウトで機能し、超高速のレイアウト読み取りメソッドで動作できることを意味します。

このパターンは実に素晴らしいので、次のヘルパーメソッド(ES5で記述)をお勧めします。

/**
 * @param fn {Function}
 * @param [throttle] {Boolean|undefined}
 * @return {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, that, 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からのものですが、まったく異なるものを提供します。フレーム内でいくつかのアイドル期間を与えます。 (通常、ブラウザーがレイアウトを計算してペイントを行った後のポイントですが、v-syncが発生するまでにまだ時間があります。)ユーザービューからページが遅れている場合でも、ブラウザーが存在するフレームがあるかもしれません。アイドリング。 rICは最大値を与えることができますが。 50ms。ほとんどの場合、タスクを完了するのに0.5〜10msしかありません。フレームライフサイクルのrICコールバックが呼び出される時点で、DOMを変更しないでください(これにはrAFを使用してください)。

最後に、scrollを使用して、遅延読み込み、無限スクロールなどのrICリスナーを調整することは非常に理にかなっています。これらの種類のユーザーインターフェイスでは、さらに調整して、その前にsetTimeoutを追加することもできます。 (したがって、100ms待機してからrIC)( デバウンス および スロットル の例)

rAFに関する記事でもあります には、「フレームライフサイクル」内のさまざまなポイントを理解するのに役立つ2つの図が含まれています。

パッシブイベントリスナー

スクロールのパフォーマンスを改善するために、パッシブイベントリスナーが考案されました。最新のブラウザは、ページスクロール(スクロールレンダリング)をメインスレッドからコンポジションスレッドに移動しました。 ( https://hacks.mozilla.org/2016/02/smoother-scrolling-in-firefox-46-with-apz/ を参照)

ただし、スクロールを生成するイベントがあります。これは、スクリプトによって防止できます(メインスレッドで発生するため、パフォーマンスの向上を取り消すことができます)。

つまり、これらのイベントリスナーの1つがバインドされるとすぐに、ブラウザーは、ブラウザーがスクロールを計算する前にこれらのリスナーが実行されるのを待つ必要があります。これらのイベントは、主にtouchstarttouchmovetouchendwheelであり、理論上はある程度keypressおよびkeydownです。 scrollイベント自体は、 not これらのイベントの1つです。 scrollイベントにはデフォルトのアクションがありません。これはスクリプトによって防止できます。

これは、preventDefaulttouchstarttouchmove、および/またはtouchendwheelを使用しない場合は、常にパッシブイベントリスナーを使用し、問題ないことを意味します。

PreventDefaultを使用する場合は、CSS touch-actionプロパティまたは少なくともDOMツリーでそれを下げます(たとえば、これらのイベントのイベント委任はありません)。 wheelリスナーの場合、mouseenter/mouseleaveでそれらをバインド/アンバインドできる場合があります。

その他のイベントの場合:パッシブイベントリスナーを使用してパフォーマンスを向上させることは意味がありません。最も重要な注意事項:scrollイベントはキャンセルできないため、neverscroll.に対してパッシブイベントリスナーを使用する意味があります。

無限スクロールビューの場合、touchmoveは不要で、scrollのみが必要なので、パッシブイベントリスナーは適用されません。

履歴書

あなたの質問に答えるために

  • 遅延読み込みの場合、無限ビューでは、イベントリスナーにsetTimeout + requestIdleCallbackの組み合わせを使用し、レイアウトの書き込み(DOMの変更)にはrAFを使用します。
  • インスタントエフェクトの場合、レイアウトの書き込み(DOMの変更)には引き続きrAFを使用します。
98

RequestAnimationFrameを使用してスロットルを調整するだけでなく、スクロールリスナーを完全に置き換えることについてもいくつか言及しました。

誰かがこれについてコメントできますか?スクロールしなくてもループを続けるので、それは悪い習慣だと思います。

0