Close
Angular React Web Components Blazor React
Open Source

Virtual Scroll コンポーネント

Ignite UI for React Virtual Scroll は、ビューポート内のアイテムと設定可能なバッファーのみを DOM 内に保持することで、大量のリストをレンダリングするコンポーネントです。スクロールバーは常にコレクション全体の範囲を表すため、10 万件のアイテムを持つ仮想リストでも通常のリストと同じようにスクロールできます。

ライブ デモ

構造

React Virtual Scroll は、表示されているアイテムと設定可能なバッファーをレンダリングし、そのトラックはコレクション全体のスクロール範囲を保持します。

React Virtual Scroll anatomy: host, track, content element, item wrappers, and over-scan buffers
1. ホスト: スクロール コンテナーです。固定の高さ (水平の場合は幅) によって、表示されるアイテムの数が決まります。
2. トラック: コレクション全体の推定される長さに合わせてサイズが設定されるスペーサーです。これにより、スクロールバーがすべてのアイテムに及びます。
3. コンテンツ要素: レンダリングされたアイテムのみを保持します。ビューポートの上にある、最初にレンダリングされたバッファー アイテムから始まり、そのサイズはレンダリングされたアイテムによって決まります。
4. アイテム ラッパー: レンダリングされたアイテムごとに 1 つ存在し、アイテム テンプレートをホストします。サイズが測定されるのはこのボックスです。
5. オーバー スキャン バッファー: ビューポートの各端を超えてレンダリングされる overScan 個のアイテム (デフォルトは 2) です。
igc-virtual-scroll                            — scrollable viewport
└── [part="virtualization-track"]             — provides the collection's scroll range
    └── [part="virtualization-content"]       — positions the rendered window
        └── div[data-vs-index]                — one wrapper per rendered item; hosts the item template

作業の開始

作業の開始 のトピックに従って Ignite UI for React をセットアップし、IgrVirtualScroll をインポートして、アイテム テンプレートとデータを指定します。React ラッパーはモジュールの読み込み時に基になる要素を登録するため、登録の呼び出しは不要です。

import { IgrVirtualScroll } from 'igniteui-react';
import type { VirtualScrollItemContext } from 'igniteui-react';

const items = Array.from({ length: 100_000 }, (_, i) => ({ name: `Item ${i}` }));

export function Employees() {
    return (
        <IgrVirtualScroll
            data={items}
            itemTemplate={(ctx: VirtualScrollItemContext<Item>) => (
                <div className="row">{ctx.index}: {ctx.value.name}</div>
            )}
            style={{ height: '400px' }}
        />
    );
}

Virtual Scroll のホストには、垂直スクロールの場合は固定の高さ、水平スクロールの場合は固定の幅が必要です。コンテンツに合わせてサイズが拡大するホストはすべてのアイテムをレンダリングするため、リストは仮想化されません。

前提条件とバージョン互換性

要件 値
パッケージ igniteui-react (MIT)
コンポーネントが最初にリリースされたバージョン 19.9.0
ピア パッケージ react および react-dom 18 または 19
アイテム テンプレート 関数から返される JSX。igniteui-webcomponents は igniteui-react の依存関係として含まれるため、個別にインストールする必要はありません。
テーマ テーマ スタイルシートを 1 度インポートします。例: igniteui-webcomponents/themes/light/bootstrap.css。

使用方法

アイテム テンプレート

Virtual Scroll のアイテム テンプレートは、アイテムとコレクション全体におけるその位置を受け取ります。位置に依存するコンテンツ (交互のスタイルや aria-posinset、aria-setsize など) には、インデックスと合計件数を使用します。

itemTemplate に、JSX を返す関数を設定します。この関数は、value (アイテム)、index、count、isFirst、isLast を持つ VirtualScrollItemContext を受け取ります。アイテム テンプレートがない場合、コンポーネントは何もレンダリングしません。

<IgrVirtualScroll
    data={employees}
    estimatedItemSize={64}
    itemTemplate={(ctx: VirtualScrollItemContext<Employee>) => (
        <IgrListItem ariaPosinset={ctx.index + 1} ariaSetsize={ctx.count}>
            <IgrAvatar slot="start" shape="circle" initials={ctx.value.initials} />
            <span slot="title">{ctx.value.name}</span>
            <span slot="subtitle">{ctx.value.email}</span>
        </IgrListItem>
    )}
    style={{ height: '480px' }}
/>

データ

Virtual Scroll の data コレクションは参照によって比較されます。リストを更新するには新しい配列を割り当てる必要があります。push などでバインドされた配列をその場で変更しても更新されません。

setEmployees(current => [...current, newEmployee]);

data が変更されると、コンポーネントは最初に変更されたインデックスより前のアイテムの測定済みサイズを保持し、それ以降のアイテムはレンダリング時に再度測定します。追加操作ではすべての既存の測定値が保持されますが、置換、フィルタリング、ソートでは最初に変更されたアイテム以降の測定値が破棄されます。

アイテムは、そのキーがレンダリングされたウィンドウ内にある間、同じ要素を保持します。keyFunction を指定しない場合はインデックスがキーになるため、ソート、挿入、削除の後もそのインデックスの要素はそのまま残り、新しいアイテムを表示します。アイテムが data 内で移動する場合は、keyFunction から安定した ID を返してください。

virtualScroll.keyFunction = (employee) => employee.id;

推定アイテム サイズ

Virtual Scroll の estimatedItemSize は、アイテムがレンダリングされて測定されるまでのピクセル単位のサイズです (デフォルトは 50)。アイテムは異なるサイズを持つことができ、測定されたサイズがそれぞれ推定値を置き換えます。

最初のレンダリングが実際の内容に近くなるよう、推定値はアイテムの平均サイズに近い値に設定してください。アイテムが測定されると、まだ測定されていないアイテムの推定値は測定済みの平均サイズに置き換えられるため、推定値がずれていてもスクロールバーと scrollToIndex は自動的に補正されます。

<IgrVirtualScroll data={employees} estimatedItemSize={80} style={{ height: '480px' }} />

アイテムはボーダー ボックスで測定されるため、マージンはアイテムのサイズに含まれません。マージンの代わりに、パディング、またはアイテム内の gap を使用してアイテム間の間隔を設定してください。

方向

Virtual Scroll の orientation は、スクロール軸を vertical (デフォルト) または horizontal に設定します。水平リストでは、各アイテムに幅を、ホストに高さを設定してください。右から左のコンテキストでは、水平スクロールとアイテムの配置が反転します。

<IgrVirtualScroll
    orientation="horizontal"
    data={employees}
    estimatedItemSize={220}
    itemTemplate={ctx => <div style={{ width: '220px' }}>...</div>}
    style={{ height: '200px' }}
/>

オーバー スキャン

Virtual Scroll の overScan は、ビューポートの各端を超えてレンダリングされる追加アイテムの数です (デフォルトは 2)。値を大きくすると、高速スクロール中の空白領域が減りますが、レンダリングする要素が増えます。

<IgrVirtualScroll data={items} overScan={6} style={{ height: '400px' }} />

インデックスへのスクロール

Virtual Scroll の scrollToIndex メソッドは、アイテムが表示される位置までスクロールします。オプションはネイティブの scrollIntoView と同じで、block (start、center、end、または nearest)、水平リスト用の inline、behavior (auto または smooth) を指定できます。まだレンダリングされていないアイテムには推定サイズしかないため、コンポーネントは到達した位置のアイテムを測定して位置を補正します。返されるプロミスは最終的な位置が確定すると解決されます。

const virtualScroll = useRef<IgrVirtualScroll>(null);

async function goTo(index: number): Promise<void> {
    await virtualScroll.current?.scrollToIndex(index, { block: 'center' });
}

block: 'nearest' を指定した場合、アイテムがすでに完全に表示されているときは位置が変更されません。コレクションの範囲外のインデックスは、最初または最後のアイテムに丸められます。

無限スクロール

Virtual Scroll の onDataRequest イベントは、リモート データからの追加のみの読み込みをサポートします。レンダリングされたウィンドウが data の末尾に近づいたとき、および読み込み済みのアイテムがビューポートを埋めない場合は最初のレンダリング時に発生します。要求されたアイテムを新しい配列として追加します。

<IgrVirtualScroll
    data={employees}
    estimatedItemSize={64}
    onDataRequest={async (event: CustomEvent<VirtualScrollDataRequest>) => {
        const { startIndex, count } = event.detail;
        const page = await fetchEmployees(startIndex, count);
        setEmployees(current => [...current, ...page]);
    }}
    style={{ height: '440px' }}
/>

一度に保留できるデータ要求は 1 つだけで、次の要求は data が次に変更された後に発行されます。data が空の場合は要求が発行されないため、最初のページは自分で読み込む必要があります。ソースにこれ以上アイテムがない場合は、追加を停止するだけでかまいません。コンポーネントが同じ開始インデックスを再度要求することはありません。

レイアウト完了

Virtual Scroll の layoutComplete プロパティは、現在のレンダリング、それによってトリガーされる測定、そしてそれらがスケジュールするレンダリングが完了したときに解決されるプロミスです。data の変更、スクロール、リサイズの後にレンダリングされたアイテムを読み取る前に、これを待機してください。

setEmployees(await fetchEmployees());
await virtualScroll.current?.layoutComplete;

使用すべき場合と使用すべきでない場合

Virtual Scroll は通常、長いリストのスクロール コンテナーとして機能し、ビューポート内のアイテムと小さなバッファーのみを DOM に保持します。一度にレンダリングできる短いリストには使用しないでください。また、アイテム テンプレートを作成する際は、各アイテムの状態を要素ではなくデータに保持してください。アイテムの要素は再利用されるため、テンプレートがバインドしていない DOM の状態は、その要素を受け取ったアイテムに表示されます。

React Virtual Scroll showing a list of 100,000 employees
使用すべき

ディレクトリ、フィード、ログ、横一列に並んだカードなど、一度にレンダリングするには大きすぎる長いリストに対して Virtual Scroll を使用します。これには、スクロール中にリモート データを読み込むリストも含まれます。

React Virtual Scroll used for a list of only five employees
使用すべきでない

短いリストは List で直接レンダリングします。列、ソート、フィルタリングを伴う表形式のデータには React Data Grid を使用します。仮想化せずに少数のリッチなアイテムを表示するには Card を使用します。

プロパティ

名前 型 デフォルト 説明
data any[] [] 仮想化するコレクション。参照で比較されます。
orientation 'vertical' | 'horizontal' 'vertical' スクロール軸。
overScan number 2 ビューポートの各端を超えてレンダリングされる追加のアイテム数。
estimatedItemSize number 50 測定されるまでのアイテムのサイズ (ピクセル)。0 以下の値は 50 として扱われます。
itemTemplate (ctx: VirtualScrollItemContext) => ReactNode null 各アイテムをレンダリングする関数。
keyFunction (item, index) => unknown null アイテムのキーを返します。これにより、アイテムが data 内で移動しても同じ要素が保持されます。指定しない場合はインデックスがキーになります。
layoutComplete Promise<void> (読み取り専用) — レンダリングとアイテムの測定が完了すると解決します。プロパティとしてではなく、ref から読み取ります。

メソッド

名前 戻り値 説明
scrollToIndex(index: number, options?: ScrollIntoViewOptions) Promise<void> index のアイテムをビューにスクロールし、最終的なスクロール位置が確定すると解決されます。

イベント

名前 引数 説明
onStateChange CustomEvent<VirtualScrollState> レンダリングされたウィンドウが変更されたときに発生します。event.detail は startIndex、endIndex、viewportSize、totalSize を持ちます。
onDataRequest CustomEvent<VirtualScrollDataRequest> レンダリングされたウィンドウが data の末尾に近づいたときに発生します。event.detail は startIndex と count を持ちます。

ハンドラーはネイティブの CustomEvent を受け取るため、ペイロードは event.detail から読み取ります。

スタイル設定

React Virtual Scroll には独自のテーマはありません。ビューポートのレイアウトのみを行い、レンダリングされたアイテムはアイテム テンプレート内の要素やコンポーネントからスタイルを継承します。

コンポーネントはライト DOM 内にレンダリングされるため、通常のセレクターでレンダリングされたアイテムに到達できます。デフォルトのスタイルはホストに 18.75rem の高さを与えます。ビューポートのサイズを変更するには、この高さを上書きしてください。

igc-virtual-scroll.employees {
    height: 480px;
}

igc-virtual-scroll.employees [data-vs-index]:nth-child(even) {
    background: var(--ig-gray-100);
}

アクセシビリティ

React Virtual Scroll は、レンダリングされたウィンドウのみを DOM に保持するため、アイテム テンプレートはコレクション全体の中での各アイテムの位置を公開する必要があります。

キーボード インタラクション

React Virtual Scroll はキー ハンドラーを追加しません。ホストはネイティブのスクロール コンテナーであり、フォーカスされたスクロール コンテナーはブラウザー標準のキー操作でスクロールします。

キー アクション
上矢印 / 下矢印 垂直リストをスクロールします。
左矢印 / 右矢印 水平リストをスクロールします。
Page Up / Page Down ビューポート 1 つ分ほどスクロールします。
Home / End コレクションの先頭または末尾にスクロールします。

ホストには tabindex がありません。フォーカス可能なコンテンツがないスクロール コンテナーがフォーカスを受け取れるかどうかはブラウザーによって異なるため、アイテムにフォーカス可能な要素が含まれない場合は、ホストに tabindex="0" を設定してください。アイテム内のフォーカスは、そのアイテムがレンダリングされたウィンドウから外れると保持されません。外れる前に意図的にフォーカスを移動してください。

スクリーン リーダー / ARIA

  • トラック、コンテンツ要素、アイテム ラッパーには role="presentation" が設定されています。ホストにはロールがありません。igc-list のようにリストのロールを持つ要素の中に配置するか、アイテムが role="listitem" をレンダリングする場合はホストに role="list" を設定してください。
  • index と count のコンテキスト プロパティを aria-posinset と aria-setsize にマップします。
  • フォーカス可能なホストには、role="list" のようにアクセシブルな名前をサポートするロールを設定し、aria-label または aria-labelledby で名前を付けてください。

アクセシビリティ準拠

インフラジスティックスは、Ignite UI for React が対象とするアクセシビリティ標準を アクセシビリティ準拠 トピックで文書化しています。このトピックは Virtual Scroll に対する準拠の主張を行うものではありません。表にはコンポーネントが提供する内容が記載されており、その後のリストにはアプリケーションが追加する必要がある内容が記載されています。

基準 コンポーネントが要件をサポートする方法
1.3.1 情報及び関係性 ラッパーには role="presentation" が設定されているため、リスト構造はホストとアイテム テンプレートから提供され、aria-posinset と aria-setsize で各アイテムの位置を公開できます。
2.1.1 キーボード ホストはネイティブのスクロール コンテナーであり、フォーカスを得るとキーボードでスクロールできます。キーボードでホストに到達できるかどうかはアプリケーションに依存します。以下のリストを参照してください。

ユーザー側の責任:

  • アイテムにフォーカス可能な要素が含まれない場合は、tabindex="0" を設定してホストをキーボードで到達可能にし、アクセシブルな名前を付けてください。
  • アイテム テンプレートから aria-posinset と aria-setsize でアイテムの位置を公開してください。
  • アイテム テンプレートに適したリスト セマンティクスを提供してください (スクリーン リーダー / ARIA を参照)。
  • 選択などのアプリケーションの状態は、レンダリングされたアイテム要素ではなくデータ内に保持してください。

トラブルシューティング

Virtual Scroll がアイテムをレンダリングしないのはなぜですか?

ホストにスクロール軸方向のサイズがない、アイテム テンプレートがない、または data が空です。ホストに固定の高さ (垂直) または幅 (水平) を設定し、アイテム テンプレートを設定して、バインドされたコレクションを確認してください。

アイテムを追加してもリストが更新されないのはなぜですか?

Virtual Scroll は data を参照によって比較するため、その場での変更は検出されません。[...items, newItem] のように新しい配列を割り当ててください。

スクロール中にスクロールバーのサイズが変わるのはなぜですか?

まだレンダリングされていないアイテムは estimatedItemSize を使用しており、アイテムが測定されるにつれて合計サイズが補正されます。

その後、まだ測定されていないアイテムの推定値は測定済みの平均サイズに置き換えられるため、スクロールするにつれて補正が収束します。最初のレンダリングが実際の内容に近くなるよう、estimatedItemSize はアイテムの平均サイズに近い値に設定してください。

リストの下の方でアイテムの位置がずれるのはなぜですか?

マージンはアイテムの測定済みサイズに含まれません。アイテムのマージンをパディングまたはアイテム内の gap に置き換えてください。

既知の制限

  • React Virtual Scroll は単一の軸を仮想化します。行と列の両方を仮想化するにはグリッドが必要です。
  • ウィンドウの移動に伴ってアイテムの要素は再利用されるため、checked をバインドしていないチェックボックスなど、アイテム テンプレートがバインドしていない DOM の状態は、その要素を受け取ったアイテムに表示されます。アイテムの状態はすべてバインドし、ユーザーによる変更はアイテムに書き戻してください。

API リファレンス

依存関係

React Virtual Scroll は他のコンポーネントへの依存関係を持たず、独自のテーマも必要としません。igniteui-react が igniteui-webcomponents と lit を含み、アイテム テンプレート内のコンポーネントにはテーマ スタイルシートが必要です。

その他のリソース

関連コンポーネント

  • List - 短いリストや、仮想化されたリストのコンテナーとして List を使用します。
  • Data Grid - 列、ソート、フィルタリングを伴う表形式のデータには Data Grid を使用します。
  • Card - 少数のリッチなアイテムの表示や、水平方向の Virtual Scroll のアイテムとしてカードを使用します。

FAQ

Virtual Scroll はどれくらいの数のアイテムを処理できますか?

React Virtual Scroll は、ビューポート内のアイテムとオーバー スキャン バッファーのみを DOM に保持するため、コレクションのサイズによってレンダリングされる要素の数は変わりません。コレクションの合計サイズがブラウザーのスクロール制限を超える場合、Virtual Scroll はコレクションをブラウザーがサポートするスクロール範囲にマッピングします。

Virtual Scroll のアイテムは同じサイズである必要がありますか?

React Virtual Scroll のアイテムは、レンダリングされた時点でそれぞれが測定されるため、異なるサイズにすることができます。アイテムが測定される前にスクロールバーを正確に保つため、estimatedItemSize をアイテムの平均サイズに近い値に設定してください。

アイテムが測定されると、まだ測定されていないアイテムの推定値は測定済みの平均サイズに置き換えられます。

Virtual Scroll を特定のアイテムまでスクロールするにはどうすればよいですか?

React Virtual Scroll の scrollToIndex メソッドを、アイテムのインデックスと、省略可能な block および behavior オプションを指定して呼び出します。このメソッドは、補正された位置が安定すると解決されるプロミスを返します。

ユーザーがスクロールしている間に Virtual Scroll にリモート データを読み込むにはどうすればよいですか?

React Virtual Scroll は onDataRequest イベントによる追加のみの読み込みをサポートします。イベントを処理し、要求されたアイテムを含む新しい配列を data に割り当てます。