無限スクロール

Inertia の無限スクロール機能は、ユーザーがスクロールするにつれて追加のコンテンツページを読み込み、従来のページネーションコントロールを置き換えます。これは、チャットインターフェース、ソーシャルフィード、フォトグリッド、商品一覧などのアプリケーションに最適です。

サーバーサイド

無限スクロール用にページネーションされたデータを設定するには、レスポンスを返す際に Inertia::scroll() メソッドを使用します。このメソッドは、適切なマージ動作を自動的に設定し、フロントエンドコンポーネント向けにページネーションメタデータを正規化します。

php
Route::get('/users', function () {
    return Inertia::render('Users/Index', [
        'users' => Inertia::scroll(fn () => User::paginate())
    ]);
});

Inertia::scroll() メソッドは、Laravel の paginate()simplePaginate()cursorPaginate() メソッドに加え、Eloquent API リソースでラップされたページネーションデータにも対応しています。詳細は Inertia::scroll() メソッド のドキュメントをご覧ください。

クライアントサイド

クライアントサイドでは、Inertia は追加のコンテンツページを自動的に読み込むための <InfiniteScroll> コンポーネントを提供します。このコンポーネントは、ページネーションされたデータを含む props のキーを指定する data プロパティを受け取ります。<InfiniteScroll> コンポーネントは、ページネーションデータに依存するコンテンツをラップする必要があります。

このコンポーネントは Intersection Observer を使用して、ユーザーがコンテンツの末尾付近までスクロールしたことを検知し、次のページを読み込むリクエストを自動的にトリガーします。新しいデータは既存のコンテンツを置き換えるのではなく、マージされます。

ローディングバッファ

バッファ距離を設定することで、コンテンツの読み込み開始タイミングを制御できます。バッファは、コンテンツの末尾に到達する何ピクセル前から読み込みを開始するかを指定します。

上記の例では、現在のコンテンツの末尾に到達する 500 ピクセル前から読み込みが開始されます。バッファを大きくすると早めに読み込まれますが、ユーザーが実際には見ない可能性のあるコンテンツも読み込まれる場合があります。

URL 同期

無限スクロールコンポーネントは、ユーザーがコンテンツをスクロールするにつれて、ブラウザ URL のクエリ文字列(?page=...)を更新します。URL は、画面上で最も多く表示されているページを反映し、上下どちらにスクロールしても更新されます。これにより、特定のページへのリンクをブックマークしたり共有したりできます。この挙動を無効にして、元のページ URL を維持することも可能です。

これは、ブログ投稿のコメントや商品ページの関連商品など、メインページの URL に影響を与えたくない副次的なコンテンツに無限スクロールを使用する場合に便利です。

リセット

フィルターやその他のパラメータが変更された場合、無限スクロールのデータを最初から再読み込みする必要があることがあります。リセットしない場合、新しい結果は既存のコンテンツにマージされ、置き換えられません。

reset ビジットオプションを使用してデータをリセットできます。

reset オプションの詳細については、Resetting props のドキュメントをご覧ください。

読み込み方向

無限スクロールコンポーネントは、開始位置または終了位置付近にスクロールした際に、両方向へコンテンツを読み込みます。この挙動は only-next および only-previous プロパティで制御できます。

デフォルトの挙動は、ユーザーが途中のページから開始し、すべてのコンテンツにアクセスするために上下両方向へスクロールする必要がある場合に特に有用です。

リバースモード

チャットアプリケーションやタイムラインなど、コンテンツが降順(最新の項目が下)で並んでいるインターフェースでは、リバースモードを有効にできます。これにより、上方向にスクロールした際に古いコンテンツが読み込まれるようになります。

リバースモードでは、読み込み方向が反転し、上にスクロールすると次のページ(古いコンテンツ)が、下にスクロールすると前のページ(新しいコンテンツ)が読み込まれます。読み込み位置の調整はコンポーネントが処理しますが、正しい順序で表示するためにコンテンツを反転させるのは利用者側の責任です。

また、リバースモードでは初期読み込み時に自動的に最下部へスクロールしますが、:auto-scroll="false" で無効化できます。

マニュアルモード

マニュアルモードでは、スクロール時の自動読み込みが無効になり、next および previous スロットを通じて、いつコンテンツを読み込むかを制御できます。利用可能なスロットプロパティやカスタマイズオプションの詳細については、Slots ドキュメントをご覧ください。

manualAfter プロパティを使用すると、一定ページ数後に自動的にマニュアルモードへ切り替えることもできます。

スロット

無限スクロールコンポーネントは、読み込み体験をカスタマイズするための複数のスロットを提供します。これらのスロットを使って、独自のローディングインジケータを表示したり、手動読み込みコントロールを作成したりできます。各スロットには、読み込み状態や読み込みをトリガーする関数を含むプロパティが渡されます。

デフォルトスロット

データ項目を描画するメインコンテンツ領域です。このスロットには読み込み状態の情報が渡されます。

ローディングスロット

ローディングスロットは、コンテンツ読み込み中で、かつカスタムの before または after スロットが提供されていない場合のフォールバックとして使用されます。これにより、デフォルトのローディングインジケータが作成されます。

Previous / Next スロット

previous および next スロットは、メインコンテンツの上下にレンダリングされ、主に手動読み込みコントロールとして使用されます。これらのスロットには、読み込み状態、フェッチ関数、モード情報などのプロパティが渡されます。

vue

loadingpreviousnext スロットには次のプロパティが渡されます。

プロパティ説明
loading現在スロットがコンテンツを読み込み中かどうか
loadingPrevious前のコンテンツを読み込み中かどうか
loadingNext次のコンテンツを読み込み中かどうか
fetchスロットの読み込みをトリガーする関数
hasMoreさらにコンテンツがあるかどうか
hasPreviousさらに前のコンテンツがあるかどうか
hasNextさらに次のコンテンツがあるかどうか
manualModeマニュアルモードが有効かどうか
autoMode自動読み込みが有効かどうか

カスタム要素

InfiniteScroll コンポーネントはデフォルトで <div> 要素としてレンダリングされます。as プロパティを使用することで、任意の HTML 要素に変更できます。

要素ターゲティング

無限スクロールコンポーネントは、URL 同期 のためにコンテンツを自動的に追跡し、要素にページ番号を割り当てます。データ項目がコンポーネントのルート要素の直接の子でない場合、実際のデータ項目を含む要素を itemsElement プロパティで指定する必要があります。

この例では、コンポーネントは #table-body 要素を監視し、新しいコンテンツが読み込まれるたびに各 <tr> にページ番号を自動的に付与します。これにより、ビューポート内で最も表示されているページに基づいて URL が正しく更新されます。

CSS セレクタを使用して、読み込みトリガー用のカスタム要素を指定することもできます。これにより、デフォルトのトリガー要素はレンダリングされず、代わりに指定したカスタム要素に対して Intersection Observer が使用されます。

あるいは、CSS セレクタの代わりにテンプレート参照(refs)を使用することもできます。これにより HTML 属性を追加する必要がなくなり、要素への直接参照が可能になります。

スクロールコンテナ

無限スクロールコンポーネントは、メインドキュメントだけでなく、任意のスクロール可能なコンテナ内でも動作します。トリガー検出や計算には、メインドキュメントのスクロールではなく、カスタムスクロールコンテナが自動的に使用されます。

複数のスクロールコンテナ

1 つのページに複数の無限スクロールコンポーネントを配置する必要がある場合があります。ただし、両方のコンポーネントが URL 同期 のためにデフォルトの page クエリパラメータを使用すると、互いに競合してしまいます。これを解決するには、それぞれのページネータにカスタムの pageName を指定します。

php
Route::get('/dashboard', function () {
    return Inertia::render('Dashboard', [
        'users' => Inertia::scroll(
            fn() => User::paginate(pageName: 'users')
        ),
        'orders' => Inertia::scroll(
            fn() => Order::paginate(pageName: 'orders')
        ),
    ]);
});

Inertia::scroll() メソッドは各ページネータから pageName を自動的に検出するため、?page= が競合することなく、?users=2&orders=3 のようにそれぞれ独立したページネーション状態を URL に反映できます。

ページネーションのページ名について詳しくは、Laravel のドキュメントをご覧ください。

プログラムによるアクセス

プログラムから読み込みアクションをトリガーしたい場合は、テンプレート参照を使用できます。

このコンポーネントは次のメソッドを公開しています。

  • fetchNext() - 次のページを手動で取得
  • fetchPrevious() - 前のページを手動で取得
  • hasNext() - 次のページが存在するか
  • hasPrevious() - 前のページが存在するか

Inertia::scroll() メソッド

Inertia::scroll() メソッドは、無限スクロールのためのサーバーサイド設定を提供します。新しいデータが既存のコンテンツを置き換えるのではなく、末尾または先頭に追加されるよう、適切なマージ動作を自動的に設定し、フロントエンドコンポーネント向けにページネーションメタデータを正規化します。

php
// すべての Laravel ページネーションメソッドで動作
Inertia::scroll(User::paginate(20));
Inertia::scroll(User::simplePaginate(20));
Inertia::scroll(User::cursorPaginate(20));

// API リソースでも動作
Inertia::scroll(UserResource::collection(User::paginate(20)));

Laravel のページネータを使用しない場合や、別の変換レイヤーを使用している場合は、scroll() が受け取る追加の引数を利用できます。

php
// データラッパーキーをカスタマイズ(デフォルトは 'data')
Inertia::scroll($customPaginatedData, wrapper: 'items');

// カスタムメタデータ解決を提供
Inertia::scroll($data, metadata: $metadataProvider);

metadata パラメータは、ProvidesScrollMetadata のインスタンス、またはそれを返すコールバックを受け取ります。コールバックは $data パラメータを受け取ります。これは Fractal のようなサードパーティ製ページネーションライブラリと統合する際に有用です。

php
use League\Fractal\Resource\Collection;

class FractalScrollMetadata implements ProvidesScrollMetadata
{
    public function __construct(protected Collection $resource) {}
    public function getPageName(): string {}
    public function getPreviousPage(): int|string|null {}
    public function getNextPage(): int|string|null {}
    public function getCurrentPage(): int|string|null {}
}

このカスタムメタデータプロバイダを scroll 関数で使用できます。

php
// インスタンスを直接使用
Inertia::scroll($data, metadata: new FractalScrollMetadata($data));

// コールバックを使用
Inertia::scroll(
    fn() => $this->transformData($data),
    metadata: fn($data) => new FractalScrollMetadata($data)
);

複数のコントローラで同じ設定を繰り返さないように、マクロを定義することもできます。

php
// AppServiceProvider の boot メソッド内
Inertia::macro('fractalScroll', function (Collection $data) {
    return Inertia::scroll(
        $data,
        metadata: fn(Collection $data) => new FractalScrollMetadata($data)
    );
});

// コントローラで使用
return Inertia::render('Users/Index', [
    'users' => Inertia::fractalScroll($fractalCollection)
]);