プログレスインジケーター

InertiaのリクエストはXHR経由で行われるため、通常、ページからページへのナビゲーション時にはブラウザのローディングインジケーターは表示されません。これを解決するために、InertiaはInertiaの訪問を行うたびにページの最上部にプログレスインジケーターを表示します。

もちろん、ご希望であれば、Inertiaのデフォルトのローディングインジケーターを無効にして、独自のカスタム実装を提供することもできます。以下では、両方のアプローチについて説明します。

デフォルト

Inertiaのデフォルトのプログレスインジケーターは、 NProgressライブラリの軽量ラッパーです。これは、createInertiaApp()関数のprogressプロパティを使用してカスタマイズできます。 createInertiaApp()関数のprogressプロパティを設定することで、Inertiaのデフォルトのローディングインジケーターを無効にすることができます。

createInertiaApp({
  progress: {
    // The delay after which the progress bar will appear, in milliseconds...
    delay: 250,

    // The color of the progress bar...
    color: '#29d',

    // Whether to include the default NProgress styles...
    includeCSS: true,

    // Whether the NProgress spinner will be shown...
    showSpinner: false,
  },
  // ...
})

Inertiaのデフォルトのローディングインジケーターを無効にするには、progressプロパティを false.

createInertiaApp({
  progress: false,
  // ...
})

カスタム

Inertiaのイベントを使用して、独自のカスタムページローディングインジケーターを設定することもできます。例として、NProgressライブラリを使用してこれを実現する方法を探ってみましょう。

まず、Inertiaのデフォルトのローディングインジケーターを無効にします。

createInertiaApp({
  progress: false,
  // ...
})

次に、NProgressライブラリをインストールします。

npm install nprogress

インストール後、NProgressの スタイルをプロジェクトに追加する必要があります。これは、CDNでホストされているスタイルのコピーを使用して行うことができます。

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.css" />

次に、NProgressとInertiaのrouterの両方をアプリケーションにインポートします。

import NProgress from 'nprogress'
import { router } from '@inertiajs/vue3'

次に、startイベントリスナーを追加しましょう。このリスナーを使用して、新しいInertia訪問が開始されたときにプログレスバーを表示します。

router.on('start', () => NProgress.start())

次に、ページの訪問が終了したときにプログレスバーを非表示にするために、finishイベントリスナーを追加しましょう。

router.on('finish', () => NProgress.done())

以上です!これで、ページからページへ移動するにつれて、プログレスバーがページに追加および削除されるようになります。

キャンセルされた訪問の処理

このカスタムプログレス実装は、正常に完了するページ訪問には最適ですが、キャンセルされた訪問も処理できるとさらに便利です。まず、中断された訪問(新しい訪問の結果としてキャンセルされたもの)の場合、プログレスバーは開始位置にリセットする必要があります。次に、手動でキャンセルされた訪問の場合、プログレスバーはページからすぐに削除する必要があります。

これは、finishイベントに提供されるevent.detail.visitオブジェクトを調べることで実現できます。

router.on('finish', (event) => {
  if (event.detail.visit.completed) {
    NProgress.done()
  } else if (event.detail.visit.interrupted) {
    NProgress.set(0)
  } else if (event.detail.visit.cancelled) {
    NProgress.done()
    NProgress.remove()
  }
})

ファイルアップロードの進捗状況

さらに一歩進んでみましょう。ファイルをアップロードしているときは、アップロードの進捗状況を反映するようにローディングインジケーターを更新すると便利です。これは、progressイベントを使用して行うことができます。

router.on('progress', (event) => {
  if (event.detail.progress.percentage) {
    NProgress.set((event.detail.progress.percentage / 100) * 0.9)
  }
})

これで、ファイルのアップロード中にプログレスバーが「じわじわと」進むのではなく、リクエストの進捗状況に基づいて実際に位置が更新されるようになります。サーバーからの応答を待つ必要があるため、ここでは進捗状況を90%に制限しています。

ローディングインジケーターの遅延

最後に実装するのは、ローディングインジケーターの遅延です。リクエストが250〜500ミリ秒以上かかって初めてローディングインジケーターを表示することが望ましい場合がよくあります。これにより、ページ訪問が高速な場合にローディングインジケーターが常に表示されるのを防ぎ、視覚的に気を散らすのを防ぎます。

遅延動作を実装するには、setTimeout関数とclearTimeout関数を使用します。まず、タイムアウトを追跡するための変数を定義することから始めましょう。

let timeout = null

次に、startイベントリスナーを更新して、250ミリ秒後にプログレスバーを表示する新しいタイムアウトを開始しましょう。

router.on('start', () => {
  timeout = setTimeout(() => NProgress.start(), 250)
})

次に、ページの訪問がタイムアウト前に終了した場合に、既存のタイムアウトをクリアするために、finishイベントリスナーを更新します。

router.on('finish', (event) => {
  clearTimeout(timeout)
  // ...
})

finishイベントリスナーでは、プログレスバーが実際に進捗状況の表示を開始したかどうかを判断する必要があります。そうしないと、タイムアウトが終了する前に誤ってプログレスバーが表示されてしまいます。

router.on('finish', (event) => {
  clearTimeout(timeout)
  if (!NProgress.isStarted()) {
    return
  }
  // ...
})

そして、最後に、progressイベントリスナーでも同じチェックを行う必要があります。

router.on('progress', event => {
  if (!NProgress.isStarted()) {
    return
  }
  // ...
}

これで、美しいカスタムページローディングインジケーターが完成しました!

完全な例

便宜上、カスタムローディングインジケーターの最終バージョンの完全なソースコードを以下に示します。

import NProgress from 'nprogress'
import { router } from '@inertiajs/vue3'

let timeout = null

router.on('start', () => {
  timeout = setTimeout(() => NProgress.start(), 250)
})

router.on('progress', (event) => {
  if (NProgress.isStarted() && event.detail.progress.percentage) {
    NProgress.set((event.detail.progress.percentage / 100) * 0.9)
  }
})

router.on('finish', (event) => {
  clearTimeout(timeout)
  if (!NProgress.isStarted()) {
    return
  } else if (event.detail.visit.completed) {
    NProgress.done()
  } else if (event.detail.visit.interrupted) {
    NProgress.set(0)
  } else if (event.detail.visit.cancelled) {
    NProgress.done()
    NProgress.remove()
  }
})