Back

高パフォーマンスインターフェースのための仮想スクロール

高パフォーマンスインターフェースのための仮想スクロール

ブラウザで50万行をレンダリングすると、インターフェースがフリーズ、カクつき、またはクラッシュする可能性が高くなります。最新のブラウザは大きなDOMツリーを処理できますが、レイアウト、スタイル計算、メモリコストにより、ノード数が増えるにつれてパフォーマンスが劇的に低下することがよくあります。仮想スクロールは、ユーザーが実際に見ることができるものだけをレンダリングすることでこの問題を解決します。この単一の制約が、データ量の多いインターフェースのパフォーマンスに関するすべてを変えます。

重要なポイント

  • 仮想スクロールは、ビューポート内に表示されているアイテム(および小さなバッファ)のみをレンダリングし、データセットのサイズに関係なくDOMノード数を一定に保ちます。
  • scrollTopから可視インデックスを計算し、それらのアイテムをレンダリングし、パディング要素を使用して完全なスクロール可能な高さをシミュレートすることで機能します。
  • 固定のアイテム高さにより、実装がシンプルになります。動的な高さには、測定のキャッシュと慎重なスクロール位置の修正が必要です。
  • ブラウザネイティブの検索(Ctrl+F)、アクセシビリティ、スクロール位置の安定性は、すべて仮想化されたリストで特別な注意が必要です。
  • React、Angular、Vue向けの成熟したライブラリが存在します。本番環境でゼロから構築することはほとんど必要ありません。

仮想スクロールとは(そして無限スクロールとの違いは)?

仮想スクロール(リスト仮想化またはウィンドウイングとも呼ばれる)は、ビューポート内に現在表示されているアイテムと、その上下の小さなバッファのみをレンダリングします。ユーザーがスクロールすると、ビューポートから外れるアイテムはDOMから削除され、新しいアイテムがその場所に挿入されます。データセット全体がDOMに完全に入ることはありません。

これは無限スクロールとは根本的に異なります。無限スクロールは、スクロールするにつれてアイテムをDOMに追加します。リストは成長し続けます。仮想スクロールは、アイテムを入れ替え、データセットのサイズに関係なくDOMノード数をほぼ一定に保ちます。

実用上の違いは重要です。10万アイテムの単純にレンダリングされたリストは、メモリ内に10万以上のDOMノードを作成する可能性があります。同じデータセットの仮想化されたリストは、任意の時点で50〜80ノードしか保持しない場合があります。

仮想化されたリストの概念的な動作

このメカニズムは、いくつかの単純なアイデアが連携して動作することに依存しています。

ビューポートウィンドウ。 スクロールコンテナに固定の高さを与えます。これにより、一度に表示されるアイテムの数が定義されます。visibleCount = Math.ceil(containerHeight / itemHeight)と呼びましょう。

インデックス計算。 ユーザーがスクロールすると、scrollTopを読み取って、可視領域の上部にあるアイテムを決定します:startIndex = Math.floor(scrollTop / itemHeight)。終了インデックスは次のようになります:endIndex = startIndex + visibleCount

スクロール位置の錯覚。 50アイテムのみをレンダリングする場合、スクロールバーは小さなリストを反映します。完全な高さをシミュレートするには、レンダリングされたアイテムの上に空のパディング要素(高さ = startIndex × itemHeight)を配置し、下にもう1つ(高さ = 残りのスペース)を配置します。スクロールバーは、完全なデータセットが存在するかのように動作します。

オーバースキャン(バッファ)。 正確に表示されているアイテムのみをレンダリングすると、高速スクロール中に不快なポップイン効果が発生します。オーバースキャンは、ビューポートの上下に数行余分にレンダリングします。通常、ユースケースに応じて5〜10アイテムです。これにより、アイテムがビューに入る前にすでにDOMに存在します。

固定vs動的なアイテム高さ

固定高さの仮想化は単純で信頼性があります。すべての計算は単純な算術です。

動的な高さははるかに困難です。レンダリング後に各アイテムを測定してそれらの測定値をキャッシュするか、事前に高さを推定して測定後に修正する必要があります。どちらのアプローチも複雑さを追加し、慎重に処理しないとスクロール位置の不安定性を引き起こす可能性があります。ユースケースで許可される場合、固定高さを設計する価値があります。

予想される実際のトレードオフ

仮想スクロールは無料ではありません。いくつかのことが壊れたり、追加の作業が必要になったりします。

  • **ブラウザのテキスト検索(Ctrl+F)**は、ほとんどのコンテンツがDOMにないため、確実に機能しなくなります。独自の検索を実装する必要があります。
  • アクセシビリティには注意が必要です。コンテナにrole="list"role="feed"、またはrole="grid"を適用します。aria-setsizearia-posinsetなどの属性を使用して、支援技術が完全なリストサイズと各アイテムの位置を理解できるようにすることができます。アイテムがアンマウントされたときにキーボードナビゲーションが壊れないように、フォーカス管理を維持します。小さなオーバースキャンバッファは、スクリーンリーダーがより多くのコンテンツが存在することを検出するのにも役立ちます。
  • スクロール位置の安定性は、データが動的に更新される場合に厄介になります。現在のスクロール位置の上にアイテムが追加または削除されると、不快なジャンプが発生する可能性があります。

フレームワーク全体のエコシステムサポート

本番環境でこれをゼロから構築する必要はほとんどありません。成熟したライブラリがエッジケースを処理します。

知っておく価値のある1つのCSS代替手段:content-visibility: autoを使用すると、ブラウザはJavaScriptなしで画面外のコンテンツのレンダリングをスキップできます。中程度のリストでペイントパフォーマンスを向上させることができますが、DOMノード数を減らすことはできず、大規模なデータセットでの完全な仮想化の代替にはなりません。

実際に使用するタイミング

仮想スクロールは複雑さを追加します。次の場合に価値があります。

  • リストが数百アイテムを超え、スクロールパフォーマンスが著しく低下している場合
  • テーブル、ログビューア、フィード、またはスプレッドシートスタイルのインターフェースを構築している場合
  • メモリ使用量が制約となる場合(モバイルデバイス、長時間実行されるセッション)

短いリストの場合、ページネーションまたは単純な遅延読み込みの方が簡単で十分なことがよくあります。

結論

ユーザーは10万のDOMノードを必要としません。10万アイテムをスクロールできるように感じる必要があります。仮想スクロールは、レンダリングコストのほんの一部でその感覚を提供します。データセットの可視スライスのみをレンダリングし、ユーザーがスクロールするにつれてアイテムを入れ替えることで、DOMノード数を低く保ち、メモリ使用量を予測可能にし、フレームレートをスムーズに保ちます。トレードオフ(壊れたCtrl+F、アクセシビリティの考慮事項、スクロール位置の管理)は現実的ですが、よく理解されており、React、Angular、Vueのライブラリエコシステムがそのほとんどを箱から出してすぐに処理します。リストがパフォーマンスを損なうほど大きい場合、仮想化は利用可能な最も効果的なツールです。

よくある質問

はい、ただし特別な注意が必要です。ほとんどの仮想化ライブラリは、単一列のリストレイアウトを想定しています。グリッドベースのレイアウトの場合、行ごとのアイテムを考慮して、可視行と列を一緒に計算する必要があります。TanStack Virtualはグリッド仮想化をネイティブにサポートしています。他のライブラリでは、複数のセルを含む単一の仮想化されたアイテムとして各行を扱う必要がある場合があります。

検索エンジンクローラーは通常、コンテンツをスクロールしないため、初期レンダリング外のアイテムはインデックス化されません。リストコンテンツにSEOが重要な場合は、ページネーションされたHTML出力またはクローラーフレンドリーな代替手段を検討してください。サーバーサイドでコンテンツをレンダリングする場合は、クライアントでのハイドレーション後にのみ仮想化を適用してください。

これは通常、オーバースキャンバッファが小さすぎるか、アイテムのレンダリングが遅すぎることを意味します。より多くの画面外アイテムが事前にレンダリングされるように、オーバースキャン数を増やしてください。また、リストアイテムが高価なレイアウト再計算をトリガーしたり、画像を同期的に読み込んだりしていないかを確認してください。アイテムコンポーネントを簡素化し、画像にプレースホルダーコンテンツを使用すると、空白フレームを大幅に減らすことができます。

はい。ナビゲーションの前に、現在のscrollTop値と対応するstartIndexを状態またはセッションストレージに保存します。ユーザーが戻ったときに、スクロールコンテナの位置を保存されたscrollTop値に復元します。ほとんどの仮想化ライブラリは、scrollToIndexまたはscrollToOffsetメソッドを公開しており、再マウント時にこれを簡単に実装できます。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay