Analog Studio

ページ遷移しないけど、ブラウザの戻るボタンを有効にする方法【pushState】

概要

シームレスな Web サイトを設計しているとユーザからの操作を受けて、ページ遷移をさせないでポップアップなどで必要な情報を表示させたいことが良くあると思います。
Javascript や CSS を使ってポップアップを表示させることは簡単ですが、ユーザが誤ってブラウザの「戻る」ボタンを押してしまうことが想定されます。

そんな時にページ遷移が発生してしまうとユーザに大きなストレスを与えてしまいます。
フォームなどでユーザから入力を受け付けていた場合にはその影響は大変大きなものです。
前の状態に戻すこと=ブラウザの「戻る」ボタン、という認識で操作してしまうことは良く見かけます。

そこで今回は、ページ遷移はしないけどブラウザの「戻る」ボタンは有効にできる小技を紹介していきます。
CSS だけでは実現できませんので、悪しからず。

history.pushState()メソッドでブラウザの履歴を操作して解決!

結論から行きましょう。
簡単に言ってしまえば、history.pushState() メソッドを使ってブラウザの履歴に状態ごとの情報を追加していくことで実現させます。

history.pushState() は pjax などのページ遷移しないで <MAIN> 要素の中身だけを書き換える処理でお馴染みですよね。

サンプルコード

とりあえず、サンプルをみて頂きましょう。
ページ全体に半透明な黒を重ねた上にポップアップを表示するだけです。

普通のJavascriptのコード

まずはベースとなるコードです。
このままだと戻るボタンでページ遷移してしまいますね。

<HTML> <!-- ボタン --> <button type="button" id="sample0_button">ポップアップを表示</button> <!-- ポップアップする要素 --> <div id="sample0"> <div>テストだよ!<BR>クリックで閉じます。</div> </div>
<CSS> #sample0{ position : fixed; /* 表示領域全体に広げる */ top : 0; bottom : 0; left : 0; right : 0; z-index : 100; /* 適当に十分大きな値 */ display : none; /* 初期状態は非表示 */ margin : 0; background-color : rgba(0,0,0,0.8); } #sample0 > div{ position : relative; top : 50%; /* 中央に配置する */ left : 50%; /* 中央に配置する */ display : inline-block; padding : 20px 40px; border : 1px solid black; background-color : white; transform : translate(-50%, -50%); /* 中央に配置する */ }
<Javascript> window.addEventListener("DOMContentLoaded", function(){ document.getElementById("sample0_button").onclick = function(){ document.getElementById("sample0").style.display = "block"; }; document.getElementById("sample0").onclick = function(){ document.getElementById("sample0").style.display = ""; // this.style.displayでもOKですが、分かりやすさの為 }; }, false);

実行結果:

テストだよ!
クリックで閉じます。

pushStateを使った場合のコード

続いて、改善したコードです。HTML と CSS は id 以外は同じです。

<Javascript> window.addEventListener("DOMContentLoaded", function(){ var n = 1; // <BUTTON>要素をクリックした回数を数える document.getElementById("sample1_button").onclick = function(){ document.getElementById("sample1").style.display = "block"; // pushStateで履歴を追加する // 第一引数:ブラウザの「戻る」「進む」動作イベントを取得した際に受け取れる状態オブジェクト(変数だと思ってOK) // 第二引数:追加するブラウザ履歴の名前ですが、現時点ではブラウザでは使用されていません // 第三引数:ブラウザに表示されるアドレスを変更する場合はここで設定(フルパスでも相対パスでもOK、#や?から始まれば現在のアドレスの末尾に追加されます) // replaceState()メソッドでポップアップ表示前の状態を現在のブラウザ履歴に上書きする window.history.replaceState({"popup": false, "element": "sample1"}, "ポップアップ非表示", ""); window.history.pushState({"popup": true, "element": "sample1", "n": n++}, "ポップアップ表示", "?popup=on"); }; document.getElementById("sample1").onclick = function(){ // document.getElementById("sample0").style.display = ""; // ブラウザの「戻る」ボタンと同じ動作をさせるhistory.back()メソッドを使う window.history.back(); }; // popstateイベントで「戻る」「進む」を操作したことを受け取り発火させる window.addEventListener("popstate", function($e){ // $e.stateから上で設定したオブジェクト(第一引数)を参照する // サンプルはコンソール上に表示するので、Ctrl+Shift+Cで開発者ツールを表示してみて下さい console.log($e.state); if($e.state.popup){ // ポップアップ表示する時 document.getElementById("sample1").style.display = "block"; } else{ // ポップアップ閉じる時 document.getElementById("sample1").style.display = ""; // 戻った状態に書き換えておく window.history.replaceState({"popup": false, "element": "sample1"}, "ポップアップ非表示", ""); } }, false); }, false);

実行結果:

テストだよ!
クリックで閉じます。

ブラウザの「戻る」でも閉じられます!

ポイント

上記コードのポイントは、以下の4点です。

  1. 初期状態 (ポップアップなどしていない状態) を識別できるようなフラグを立てておく (history.replaceState() メソッド)
  2. 変化した状態 (ポップアップなどを表示した状態) を history.pushState() メソッドで追加する
  3. addEventListener() メソッドで "popstate" イベントを取得して状態を変化させる DOM 操作をする
  4. 通常のクリックイベントなどでも同様の DOM 操作をする

ここで popstate イベントとクリックイベントで同じ処理内容で良ければ、関数化しておくと良いでしょう。
複雑な処理をさせる場合には変更・修正も容易になります。

また注意点として、pushState や replaceState を実行してもブラウザのページにはなんの変化も起きません (ページ遷移しない) ので popstate でどの操作を拾って全ての処理を自分で組む必要があります。
普通のブラウジングと同じようにページ遷移させたければ、明示的に location.href などを書き換える必要があります。

コードの解説

では、上記サンプルコードの解説をしていきます。
ソース上にコメントを入れていますので、これだけで理解できた方は以下を読む必要はありません。

history.replaceStateメソッド

まずは、現在のブラウザ履歴を更新 (上書き) する history.replaceState() メソッドについて解説します。

window.history.replaceState({データ}, "履歴のタイトル", "[URL]"); // window. はなくてもOK -> history.replaceState(); でも動く

このメソッドでは (今いる) ブラウザ履歴を更新することができます。
pushState する前の初期状態データを保存しておく際などに便利です。

第一引数には、popstate イベントで受け取ることができるオブジェクト (Javascript では連想配列と等価) を設定します。
これは popstate した時の識別やフラグなどを保存しておくのに便利です。
(URL を変えてそれを識別子やフラグとしても利用できますが、こちらの方が見た目に現れずスマートです)

第二引数には、履歴のタイトルをつけることができます。
但し、現時点のブラウザはこの値を使用しておらず設定してもどこにも使われません。
将来使われるかもしれないので分かりやすい名前を付けておくと良いと思います。

第三引数には、ブラウザのアドレスバーに表示される URL を設定できます。
どんなアドレスでも入れることができます。その URL が実際に存在するかしないかは関係ありません。
ページ遷移しないので、ただブラウザのアドレスバーの表示が変わるだけです。
※自由に設定できるといっても、自分のドメイン内の URL だけですのでそこはご注意下さい。

history.pushStateメソッド

続いて history.pushState() メソッドについて解説します。

window.history.pushState({データ}, "履歴のタイトル", "[URL]"); // window. はなくてもOK -> history.pushState(); でも動く

先ほどの replaceState と違い、今度は新たな履歴が追加されます。
これによりブラウザの「戻る」ボタン等での操作を受け付けることができるようになります。

pjax ではページ遷移せずに DOM だけを更新していページ遷移しているように見せていますが、通常のブラウジングと同じように「戻る」「進む」といった操作を使えるようにする為にページを書き換えるたびに pushState して履歴を追加しています。

history.pushState() の引数は history.replaceState() と同じです。
両社で違うのは履歴が追加されるか、されないかだけです。

popstateイベント

最後に popstate イベントについてです。
history.replaceState() や history.pushState() した場合にはブラウザの「戻る」「進む」でページ遷移が起こらないので、これらの操作を Javascript で受け取って処理する必要があります。

window.addEventListener("popstate", function($e){ // ブラウザの「戻る」「進む」操作をした際に発火する // pushStateやreplaceStateした時に設定したオブジェクトは$e.stateに格納されていて利用できる /* やりたい処理 */ }, false);

ここで、history.pushState() などで実際に存在する URL を指定していてもそのページ内での「戻る」「進む」操作ではその存在する URL へページ遷移は起こりません。
但し、別のページから「実際に存在する URL を pushStateした履歴」に「戻る」「進む」操作で来た場合はその URL のページが表示されますので、注意して下さい。

popstate イベントを受け取ったら、設定しておいた履歴ごとのオブジェクトや URL などを参照してどの履歴を表示させたいのかを判断して、必要な処理をしていきます。

サンプルコードでは $e.state.popup に論理値 (true/false) を設定しているのでポップアップの表示有無を切り替えてています。

まとめ

以上、ページ遷移のない時でもブラウザの「戻る」「進む」操作を受け付けるための小技紹介でした。

ポップアップなどは便利ですが、元の画面に戻る (ポップアップを閉じる) 方法が分からずに「戻る」を操作してしまうユーザが少なからずいることを念頭にページ設計をしていきましょう。
Javascript に頼らずとも、閉じればいいんだ、と分かる UI にしておくことも大事ですが、より使いやすさを重視するのも一考です。