要約: DOM 要素を再利用し、ビューポートから遠く離れた要素を削除します。データの遅延を考慮してプレースホルダを使用します。無限スクロールのデモとコードをご覧ください。
無限スクロールはインターネットのいたるところに存在します。Google Music のアーティスト リストは 1 つ、Facebook のタイムラインは 1 つ、Twitter のライブフィードも 1 つです。下へスクロールして、下部に達する前に、どこからともなく新しいコンテンツが魔法のように表示されます。ユーザーにとってシームレスなエクスペリエンスであり、魅力は明らかです。
ただし、無限スクロールの背後にある技術的な課題は、見た目よりも困難です。「正しいこと」をしようとすると、さまざまな問題に直面します。最初は、コンテンツがフッターを押し続けるため、フッターのリンクが実質的にアクセスできなくなるなどの簡単な問題が発生します。しかし、問題は難しくなります。ユーザーがスマートフォンを縦向きから横向きに変えた際のサイズ変更イベントをどのように処理するか、リストが長くなりすぎたときにスマートフォンが停止しないようにするにはどうすればよいか。
The right thing™
そのため、パフォーマンス基準を維持しながら、これらの問題に再利用可能な方法で対処する方法を示したリファレンス実装を作成しました。
この目標を達成するために、DOM リサイクル、墓石、スクロール アンカーの 3 つの手法を使用します。
デモケースは、メッセージをスクロールできるハングアウトのようなチャット ウィンドウです。まず、無限のチャット メッセージ ソースが必要です。技術的には、現在利用可能な無限スクロールはどれも真の無限ではありませんが、これらのスクロールに投入できるデータの量を考えると、無限スクロールとみなしてかまいません。簡素化のため、一連のチャット メッセージをハードコードし、メッセージ、作成者、場合によっては画像アタッチメントをランダムに選択し、実際のネットワークに少し近づけるように人工的な遅延を少し追加します。

DOM のリサイクル
DOM リサイクルは、DOM ノード数を抑えるために活用されていない手法です。一般的な考え方は、新しい DOM 要素を作成するのではなく、すでに作成されている画面外の DOM 要素を使用することです。DOM ノード自体は安価ですが、メモリ、レイアウト、スタイル、ペイントに追加のコストが発生するため、無料ではありません。低価格帯のデバイスでは、ウェブサイトの DOM が大きすぎて管理できない場合、完全に使用できなくなるだけでなく、動作が著しく遅くなります。また、スタイルの再レイアウトと再適用(クラスがノードに追加または削除されるたびにトリガーされるプロセス)は、DOM が大きくなるほどコストが増大します。DOM ノードをリサイクルすることで、DOM ノードの総数を大幅に減らし、これらのプロセスをすべて高速化できます。
最初のハードルはスクロール自体です。特定の時点で DOM で使用可能なすべてのアイテムのごく一部しか存在しないため、理論上存在するコンテンツの量をブラウザのスクロールバーに正しく反映させる別の方法を見つける必要があります。1 ピクセル x 1 ピクセルのセンチネル要素と変換を使用して、アイテムを含む要素(ランウェイ)に目的の高さを強制的に設定します。ランウェイ内のすべての要素を独自のレイヤに昇格させ、ランウェイのレイヤ自体を完全に空にします。背景色はなし。滑走路のレイヤが空でない場合、ブラウザの最適化の対象外となり、数十万ピクセルの高さのテクスチャをグラフィック カードに保存する必要があります。モバイル デバイスでは絶対に不可能です。
スクロールするたびに、ビューポートがランウェイの端に十分に近づいているかどうかを確認します。視界から外れたアイテムがランウェイの下部に移動し、新しいコンテンツが追加されるため、ランウェイが延長されます。
反対方向にスクロールする場合も同様です。ただし、スクロールバーの位置が一定になるように、実装でランウェイを縮小することはありません。
Tombstone
前述のように、Google はデータソースを現実世界のもののように動作させようとしています。ネットワーク レイテンシなど、つまり、ユーザーがフリック スクロールを使用している場合、データがある最後の要素を簡単にスクロールできます。その場合、Tombstone アイテム(プレースホルダ)が配置されます。このアイテムは、データが届くと実際のコンテンツを含むアイテムに置き換えられます。墓石もリサイクルされ、再利用可能な DOM 要素用の個別のプールがあります。これは、墓石からコンテンツが入力されたアイテムへのスムーズな遷移を実現するために必要です。そうしないと、ユーザーに非常に不快感を与え、実際にはユーザーがフォーカスしていた内容を見失う可能性があります。

ここでの興味深い課題は、アイテムごとのテキスト量や添付画像が異なるため、実際のアイテムの高さが墓石アイテムよりも大きくなる可能性があることです。この問題を解決するため、データが届き、ビューポートの上に墓石が置き換えられるたびに現在のスクロール位置を調整し、スクロール位置をピクセル値ではなく要素にアンカーします。このコンセプトはスクロール アンカーと呼ばれます。
スクロール アンカー
スクロール アンカーは、墓石が置き換えられるときと、ウィンドウのサイズが変更されるとき(デバイスがフリップされたときも)の両方で呼び出されます。ビューポート内で最も上部に表示されている要素を特定する必要があります。その要素は部分的にしか表示されない可能性があるため、ビューポートが始まる要素の上部からのオフセットも保存します。

ビューポートのサイズが変更され、ランウェイが変更された場合、ユーザーにとって視覚的に同じ状態に戻すことができます。勝ち!ただし、ウィンドウのサイズが変更されると、各アイテムの高さが変更される可能性があります。アンカーされたコンテンツをどの程度下に配置すればよいか、どうすればわかりますか?そのようなことはありません。これを把握するには、アンカーされたアイテムの上にすべての要素をレイアウトし、それらの高さをすべて合計する必要があります。これにより、サイズ変更後に大幅な一時停止が発生する可能性があります。代わりに、上記のすべてのアイテムが墓石と同じサイズであると仮定し、それに応じてスクロール位置を調整します。要素がランウェイにスクロールされると、スクロール位置が調整され、レイアウト作業が実際に必要なときに効果的に延期されます。
レイアウト
重要な詳細情報であるレイアウトについては、説明を省略しました。通常、DOM 要素を再利用するたびにランウェイ全体が再レイアウトされるため、60 フレーム/秒の目標を大幅に下回ります。これを回避するため、レイアウトの負担を自分自身に負わせ、変換で絶対位置の要素を使用します。これにより、実際は空きスペースしかないにもかかわらず、滑走路のさらに上にあるすべての要素がまだスペースを占有しているように見せることができます。レイアウトは自分で行うため、各アイテムの最終的な位置をキャッシュに保存し、ユーザーが後方にスクロールしたときに、キャッシュから正しい要素をすぐに読み込むことができます。
理想的には、アイテムは DOM に接続されたときに 1 回だけ再描画され、ランウェイ内の他のアイテムの追加や削除の影響を受けないようにします。可能ですが、最新のブラウザでのみ可能です。
最先端の調整
最近、Chrome に CSS Containment のサポートが追加されました。この機能により、デベロッパーは要素がレイアウトとペイントの境界であることをブラウザに指示できます。ここではレイアウトを自分で行うため、制限の優れたアプリケーションです。ランウェイに要素を追加するたびに、他のアイテムが再レイアウトの影響を受けないことを認識しています。したがって、各アイテムは contain: layout
を取得する必要があります。また、ウェブサイトの他の部分に影響を与えないように、ランウェイ自体にもこのスタイル ディレクティブを適用する必要があります。
検討したもう 1 つの方法は、ユーザーが要素のリサイクルを開始して新しいデータを読み込むのに十分な距離をスクロールしたときに検出するメカニズムとして IntersectionObservers
を使用することです。ただし、IntersectionObserver は(requestIdleCallback
を使用する場合と同様に)レイテンシが高いように指定されているため、IntersectionObserver を使用すると、レスポンスが悪くなるように感じることがあります。scroll
イベントを使用した現在の実装でも、スクロール イベントが「ベスト エフォート」ベースでディスパッチされるため、この問題が発生します。最終的には、Houdini のコンポジタ ワークレットがこの問題に対する高忠実度のソリューションになるでしょう。
まだ完璧ではありません
現在の DOM リサイクルの実装は、実際に画面に表示されている要素のみを処理するのではなく、ビューポートを通過するすべての要素を追加するため、理想的ではありません。つまり、非常に速くスクロールすると、Chrome にレイアウトとペイントの処理が大量に発生し、処理が追いつかなくなるのです。背景以外は何も表示されなくなります。致命的ではありませんが、改善すべき点です。
優れたユーザー エクスペリエンスと高いパフォーマンス基準を組み合わせようとすると、単純な問題が複雑になる可能性があることをご理解いただければ幸いです。プログレッシブ ウェブアプリがモバイル スマートフォンのコア エクスペリエンスになりつつあるため、この点はさらに重要になり、ウェブ デベロッパーはパフォーマンスの制約を考慮したパターンの使用に引き続き投資する必要があります。
すべてのコードは リポジトリにあります。再利用できるように最善を尽くしましたが、npm の実際のライブラリとして、または個別のリポジトリとして公開されることはありません。主な用途は教育です。