[解決済み] スクロールイベント:requestAnimationFrame VS requestIdleCallback VS パッシブイベントリスナー
質問
ご存知のように、ユーザーがスクロールしているときにUXを良くするために、スクロールリスナーをデバウンスすることがよく勧められます。
しかし、私はしばしば
ライブラリ
と
記事
では、Paul Lewis のような影響力のある人々が
requestAnimationFrame
. しかし、Web プラットフォームの急速な進歩に伴い、いくつかのアドバイスが時間の経過とともに非推奨となる可能性があります。
私が見た問題は、パララックスウェブサイトの構築、または無限スクロールとページネーションの処理のように、スクロールイベントを処理するための非常に異なるユースケースがあることです。
私は、UXの面で違いを生み出すことができる3つの主要なツールを見ています。
そこで、ユースケースごとに(私は2つしか持っていませんが、あなたは他のものを思いつくことができます)、非常に良いスクロール体験をするために、今どんなツールを使うべきかを知りたいのです。
より正確に言うと、私の主な質問は、無限スクロールのビューとページネーション(一般的に視覚的なアニメーションをトリガーする必要はありませんが、良いスクロール体験が必要です)に関連するもので、次のように置き換えるのが良いでしょうか?
requestAnimationFrame
のコンボで置き換える方が良いのでしょうか?
requestIdleCallback
+ パッシブスクロールイベントハンドラ ? また、どのような場合に
requestIdleCallback
を使用して API を呼び出したり、API のレスポンスを処理してスクロールのパフォーマンスを向上させる意味があるのか、それともブラウザがすでに処理していることなのかについても疑問に思っています。
どのように解決するのですか?
この質問は少し古いのですが、これらのテクニックの多くが誤って使用されているスクリプトをよく見かけるので、回答したいと思います。
一般的に、あなたが尋ねたすべてのツール (
rAF
,
rIC
やパッシブリスナー)は素晴らしいツールで、すぐになくなることはないでしょう。しかし、それらを使用する理由を知っておく必要があります。
始める前に。パララックス効果やスティッキー要素などのスクロール同期/スクロール連動効果を生成する場合、rIC
,
setTimeout
は即座に反応したいので意味がない。
requestAnimationFrame
rAF
は、ブラウザがドキュメントの新しいスタイルとレイアウトを計算しようとする直前の、フレームのライフサイクル内のポイントを提供します。これが、アニメーションに使用するのに最適な理由です。まず、ブラウザがレイアウトを計算する頻度(正しい頻度)よりも多く、または少なく呼び出されることはありません。次に、ブラウザがレイアウトを計算する直前に呼び出されます(適切なタイミング)。実際
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
なぜなら、ブラウザがレイアウトをレンダリングするよりも頻繁にブラウザにレイアウトの変更を要求することは意味がないからです。
しかし
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で書かれています)をお勧めします。
/**
* 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
APIからは、以下のような感じでしょうか。
rAF
と似ていますが、全く異なるものを提供します。これは、フレーム内のアイドル時間を提供します。(通常、ブラウザがレイアウトを計算し、ペイントを行った後の時点ですが、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/ )
しかし、スクロールを発生させるイベントがあり、これはスクリプトによって防ぐことができます (これはメインスレッドで発生するため、パフォーマンスの向上を元に戻すことができます)。
つまり、これらのイベント リスナーがバインドされるとすぐに、ブラウザはスクロールを計算する前に、これらのリスナーが実行されるのを待つ必要があります。これらのイベントは主に
touchstart
,
touchmove
,
touchend
,
wheel
であり、理論的にはある程度
keypress
であり
keydown
. は
scroll
イベントそのものは
ではなく
のいずれかのイベントです。その
scroll
イベントにはデフォルトのアクションはなく、スクリプトによって阻止することができます。
これは、もしあなたが
preventDefault
を使わなければ
touchstart
,
touchmove
,
touchend
または
wheel
というように、常にパッシブなイベントリスナーを使用するようにすれば、問題ないでしょう。
preventDefaultを使用する場合、CSSで代用できるかどうか確認してください。
touch-action
プロパティで代用できるか、少なくとも DOM ツリーでそれを下げられるかを確認してください (たとえば、これらのイベントに対してイベントデリゲーションを行わないなど)。の場合
wheel
リスナーの場合、バインド/アンバインドは
mouseenter
/
mouseleave
.
その他のイベントの場合 パフォーマンスを向上させるために、受動的なイベントリスナーを使用することは意味がありません。最も注意すべきは
は
scroll
イベントはキャンセルすることができないので、それは
は決して
に受動的なイベントリスナーを使用することは理にかなっています。
scroll
.
無限スクロールのビューの場合は
touchmove
は不要であり、必要なのは
scroll
だけなので、パッシブイベントリスナーは適用されません。
レジュメ
ご質問にお答えします。
-
遅延ロードのために、Infinite Viewは以下の組み合わせを使用します。
setTimeout
+requestIdleCallback
をイベントリスナーに使用しrAF
を使用してください(DOM 変異)。 -
インスタントな効果を得るには、まだ
rAF
を使用してください。
関連
-
[解決済み] jQueryで要素にスクロールする
-
[解決済み] JavaScriptでページの一番上までスクロールする?
-
[解決済み] 動的に生成された要素にイベントバインディングを行うか?
-
[解決済み] イベントバブリング、キャプチャーとは何ですか?
-
[解決済み] イベントを発生させた要素のIDを取得する
-
[解決済み] JavaScriptやデバッグでDOMノード上のイベントリスナーを見つけるには?
-
[解決済み】パッシブイベントリスナーとは何ですか?
-
[解決済み] javascriptで2つの数値を連結する方法は?
-
[解決済み] Prototypeを使ってtextareaを自動サイズ調整するには?
-
[解決済み] JavaScriptデータフォーマット/プリティプリンタ
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] javascriptで2つの数値を連結する方法は?
-
[解決済み] 文字列がすべて同じ部分文字列で構成されているかどうかを調べるにはどうすればよいですか?
-
[解決済み] javascript の関数から `undefined` と `null` のどちらを返すのが良いのでしょうか?
-
[解決済み] JavaScriptで、ある文字列が別の文字列の中に出現するすべてのインデックスを見つけるにはどうすればよいですか?
-
[解決済み] TypeScriptのdeclare classとinterfaceの違いとは?
-
[解決済み] Javascript 空の配列の削減
-
[解決済み] Javascriptで動的に命名されたメソッドを呼び出すにはどうすればよいですか?
-
[解決済み] $.ajax実行中にローディングイメージを表示する
-
[解決済み] Promise : then vs then + catch [重複].
-
[解決済み] Prototypeを使ってtextareaを自動サイズ調整するには?