ご存知のように、ユーザーがスクロールしているときにUXがより良くなるように、スクロールリスナーをデバウンスすることをお勧めします。
ただし、Paul Lewisのような影響力のある人がrequestAnimationFrame
の使用を推奨している場合、 ライブラリ および 記事 をよく見つけました。ただし、Webプラットフォームが急速に進歩するにつれて、時間の経過とともに一部のアドバイスが非推奨になる可能性があります。
私が見る問題は、視差ウェブサイトの構築、無限スクロールとページネーションの処理など、スクロールイベントを処理するための非常に異なるユースケースがあることです。
UXの用語に違いをもたらすことができる3つの主要なツールがあります。
だから、ユースケースごとに(私は2つしか持っていませんが、他のものを思い付くことができます)、非常に良いスクロール体験をするために今どんな種類のツールを使用する必要がありますか?
より正確には、私の主な質問は、無限スクロールビューとページネーション(通常は視覚的なアニメーションをトリガーする必要はありませんが、優れたスクロールエクスペリエンスが必要です)に関連します。requestAnimationFrame
をrequestIdleCallback
+パッシブスクロールイベントハンドラのコンボ?また、APIの呼び出しやAPI応答の処理にrequestIdleCallback
を使用してスクロールのパフォーマンスを向上させるのが理にかなっているのか、それともブラウザが既に処理しているのでしょうか。
この質問は少し古いですが、これらのテクニックの多くが誤用されているスクリプトをよく見かけるので、答えたいと思います。
一般的に、すべての質問されたツール(rAF
、rIC
、およびパッシブリスナー)は優れたツールであり、すぐに消えることはありません。しかし、それらを使用する理由を知っておく必要があります。
始める前に:パララックスエフェクト/スティッキーエレメントのようなスクロール同期/スクロールリンクエフェクトを生成する場合、rIC
、setTimeout
を使用したスロットルは、すぐに反応したいので意味がありません。
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つがバインドされるとすぐに、ブラウザーは、ブラウザーがスクロールを計算する前にこれらのリスナーが実行されるのを待つ必要があります。これらのイベントは、主にtouchstart
、touchmove
、touchend
、wheel
であり、理論上はある程度keypress
およびkeydown
です。 scroll
イベント自体は、 not これらのイベントの1つです。 scroll
イベントにはデフォルトのアクションがありません。これはスクリプトによって防止できます。
これは、preventDefault
、touchstart
、touchmove
、および/またはtouchend
でwheel
を使用しない場合は、常にパッシブイベントリスナーを使用し、問題ないことを意味します。
PreventDefaultを使用する場合は、CSS touch-action
プロパティまたは少なくともDOMツリーでそれを下げます(たとえば、これらのイベントのイベント委任はありません)。 wheel
リスナーの場合、mouseenter
/mouseleave
でそれらをバインド/アンバインドできる場合があります。
その他のイベントの場合:パッシブイベントリスナーを使用してパフォーマンスを向上させることは意味がありません。最も重要な注意事項:scroll
イベントはキャンセルできないため、neverはscroll
.に対してパッシブイベントリスナーを使用する意味があります。
無限スクロールビューの場合、touchmove
は不要で、scroll
のみが必要なので、パッシブイベントリスナーは適用されません。
あなたの質問に答えるために
setTimeout
+ requestIdleCallback
の組み合わせを使用し、レイアウトの書き込み(DOMの変更)にはrAF
を使用します。rAF
を使用します。RequestAnimationFrameを使用してスロットルを調整するだけでなく、スクロールリスナーを完全に置き換えることについてもいくつか言及しました。
誰かがこれについてコメントできますか?スクロールしなくてもループを続けるので、それは悪い習慣だと思います。