1クール続けるブログ

とりあえず1クール続けるソフトウェアエンジニアの備忘録

JSの履歴APIを使って同一ページ内での疑似遷移でもブラウザバックできるようにする

タイトルを付けるのが難しかった…未来の自分が見返したときにこれでピンとくるのだろうか?
例えば、下記のようなケースで同一ページでもユーザからするとページ遷移したかのように感じるということがあります。

  • Ajax通信でデータをフェッチし、既存のデータをリプレイスする形で画面に反映させる
  • サマリーページと詳細ページを同一HTML内で返却していて、JSによって表示を切り替えている

このような場合に、何もしなければ、ページ内でのまるで遷移したかのような行動は反映されていないため、「戻ると想定していたページと違う」ことが予想されます。この行動をブラウザの履歴に反映させようというやつです。

JSのHistory APIを使う

実質、下記を見れば終わり!
どうしてMDNのサイトってこんなに見やすいのだろうか?神か?ありがとうございます!

developer.mozilla.org

pushstateで履歴を挿入する

履歴エントリ群はスタックのデータ構造を持っている。history.pushState()を使うことで、そのスタックに履歴エントリを追加することができる。
最初に出した例で言うと、Ajax通信が正常終了したときにそのAjax通信を再現できる情報を詰めて突っ込めば良さそう。キャッシュ可能なデータならば、Ajax通信のレスポンスヘッダにCache-Controlヘッダ付けて、privateかつmax-age: 120にでもしておけば、2分以内にブラウザバックを押したときに再度通信が走ることはないので快適かもしれません。

    let stateObj = {
        page: 2,
    };
    
    let title = `${document.title} - 2` 
    
    /**
     * JSDocの使い方間違っているけど、分かりやすいので…
     * @param {Object} stateObj エントリと一緒に保存できるオブジェクト(空でもOK)
     * @param {string} title 文書のタイトル(Safari 以外のブラウザはこのパラメータを無視)
     * @param {string} URL 絶対パス相対パス問わず
     */
    history.pushState(stateObj, title, "/product?page=2");

popstateをフックする

サマリーページと詳細ページを同一HTML内で返却していて、JSによって表示を切り替えている

最初に挙げたこのケースの場合には、history.pushState()を使わなくとも、history.popStateへのhookとURLの#(ハッシュ)を使えば実現可能。

  • 詳細への遷移の際に、URLの#(ハッシュ)にどの要素を展開しているかを残しておく
  • ユーザがブラウザバックをしたときに発生するイベント popstateをフックして処理を行う
// pureJS
window.onpopstate = function() {
    let currentState = history.state;  // 現在の履歴のstateを読み込む
}

// jQuery
$(window).on('popState', function () {
    if (location.hash != undefined) {
        $(location.hash).css('display', 'block');
        $('#summmary').css('display', 'none');
    }
})

感想

SPAのページ遷移もHistory APIを使って実現しているのかなと思いました。
windowオブジェクトにぶら下がっているオブジェクトを全然把握できていなくて、JSで実現できることは自分の想像以上に多くあるんだろうなとも感じますね。