ブラウザの「戻る」「進む」ボタンが押されたことを検知する

プログラミング
公開 2025年10月18日
Article Image

はじめに

ブラウザの「戻る」「進む」ボタンでページ遷移したことを検知するにはどうしたらよいでしょうか?

popstate イベント 1 を使用する方法がありますが、popstate は 通常のページ遷移(別ページに移動) では発生しません。同一ページ内で pushState による履歴管理をしている場合のみ意味があります。別ページに遷移した時点で直前のページは破棄されますので、popstate を含む全てのイベントハンドラも破棄されます。また、「戻る」「進む」のどちらなのかを判定する情報は提供されません。

performance.navigation.type 2 を使用する方法もあります。こちらは別ページへの遷移で使用できますが、HTML のキャッシュが無効の場合、type の値は常に 1: TYPE_RELOAD になってしまい、通常のページ遷移と区別することができません。キャッシュが有効な場合でも「戻る」「進む」の時の type の値は 2: TYPE_BACK_FORWARD ですので、popstate と同様に「戻る」「進む」のどちらなのかを判定する情報は提供されません。また、performance.navigation.type は非推奨の機能になったため、今後はブラウザが対応しなくなる可能性があります。

ここでは sessionStoragehistory.state 3 を併用した方法をご紹介します。
各方法の違いは以下となります。

方法 戻る/進む判定 キャッシュ無効時 備考
popstate × 4 SPA 向け
performance.navigation.type 〇(古い仕様) ×(常に reload) 廃止予定
sessionStorage + history.state 通常のページ遷移向け

コード

  • 通常のページ遷移
  • 戻るボタンでの遷移
  • 進むボタンでの遷移
  • どれでもない

これらを判定するコードは以下です。

window.addEventListener('pageshow', function () {
    // sessionStorage から遷移前のタイムスタンプを取得
    const prevTimestamp = Number(sessionStorage.getItem('prevTimestamp') ?? -1);

    // history.state に保存したタイムスタンプを取得
    let currentTimestamp = history.state?.timestamp ?? null;

    // 新しいページ遷移かどうかを判定
    if (!currentTimestamp) {
        console.log('新しいページ遷移');
        currentTimestamp = Date.now();
        if (currentTimestamp === prevTimestamp) {
            currentTimestamp += 1; // 同じタイムスタンプになることは無いと思うが念のため
        }

        // history.state にタイムスタンプを保存
        history.replaceState({
            timestamp: currentTimestamp
        }, '');
    } else {
        console.log('履歴内の移動');
        if (prevTimestamp > currentTimestamp) {
            console.log('戻るボタン');
        } else if (prevTimestamp < currentTimestamp) {
            console.log('進むボタン');
        } else {
            console.log('どれでもない');
        }
    }

    sessionStorage.setItem('prevTimestamp', currentTimestamp);
});

解説

たとえば、以下のようにページ遷移した場合を例にします。

履歴の順序 タイムスタンプ ページ
1 15:00 page1
2 15:10 page2
3 15:20 page3
4 * 15:30 page1

* は現在表示しているページです。
新しいページ遷移のたびに history.sate にタイムスタンプを設定していくので、以下のようになっています。

履歴の順序 history.state.timestamp ページ
1 15:00 page1
2 15:10 page2
3 15:20 page3
4 * 15:30 page1

Session Storage に保存している prevTimestampは 15:30 です。

戻る

ここで「戻る」ボタンで遷移すると、3 番目のページに移動して、history.state.timestamp は 15:20 です。

prevTimestamp(15:30) > history.state.timestamp(15:20)

なので「戻る」だとわかります。

履歴の順序 history.state.timestamp ページ
1 15:00 page1
2 15:10 page2
3 * 15:20 page3
4 15:30 page1

prevTimestampは 15:20 に更新されます。

進む

次に「進む」ボタンで遷移すると、4 番目のページに移動して、history.state.timestamp は 15:30 です。

prevTimestamp(15:20) < history.state.timestamp(15:30)

なので「進む」だとわかります。

履歴の順序 history.state.timestamp ページ
1 15:00 page1
2 15:10 page2
3 15:20 page3
4 * 15:30 page1

prevTimestampは 15:30 に更新されます。

新しいページ遷移

今度は「戻る」「進む」ではなく通常のページ遷移を行ってみます。ページ内のリンクや、ブラウザの URL 欄に直接入力して、page3 を表示したとします。
この時、history には新しいエントリが追加されます。

新しいエントリですので、history.state には何も入っていません。

そのため「新しいページ遷移」とわかります。

次の判定に備えて現在のタイムスタンプを history.state に設定します。

履歴の順序 history.state.timestamp ページ
1 15:00 page1
2 15:10 page2
3 15:20 page3
4 15:30 page1
5 * 15:40 page3

prevTimestampは 15:40 に更新されます。

どれでもない

再度、通常のページ遷移を行ってみます。先ほどと同様に page3 に通常のページ遷移を行います。
あるいはリロードでも同様の動きになります。

この時、history に新しいエントリは追加されません
history.state.timestamp には 15:40 が設定されています。

prevTimestamp(15:40) = history.state.timestamp(15:40)

これが「どれでもない」のパターンです。 履歴の状態は変化しません。

履歴の順序 history.state.timestamp ページ
1 15:00 page1
2 15:10 page2
3 15:20 page3
4 15:30 page1
5 * 15:40 page3

prevTimestampは 15:40 のままになります。

ブラウザの履歴には、現在表示しているページの URL と同じ URL のページを表示した場合、履歴は増えない という仕様があるようです。履歴の先頭に限らず、page1 や page2 を表示しているタイミングで同じページを再表示しても履歴は増えません。

「どれでもない」と表現しましたが、「同じページに遷移した」という表現のほうが適切かもしれません。

補足

  • pageshow イベント 5
    キャッシュからページを復元した場合、load イベントは発生しないので pageshow を使用しています。
  • history.state
    コード例では history.replaceStatestate を上書きしていますが、別の用途で state を使用している場合は、相互に上書きしないように考慮が必要です。

  • sessionStorage
    sessionStorage は同一オリジン・同一タブでのみ共有されるデータなのでオリジンをまたがる場合の判定には使用できません。

脚注

  1. Window: popstate イベント

  2. PerformanceNavigation.type

  3. History: state プロパティ

  4. キャッシュが無効だと「戻る」「進む」ボタンで遷移したタイミングでリロードするため、popstate ハンドラは破棄されます。

  5. Window: pageshow イベント