SPAはブラウザバックが使えない
SPAは1つのHTML内で画面遷移する特性上、SPA内で画面遷移してもブラウザバックで1つ前の画面に戻ることができない。
しかし、ユーザーはそのサイトがSPAなのかなど知るよしもなく、画面遷移してるのにブラウザバックできないのは不便だ。
そこで、どうにかしてブラウザバックを効かせる方法を考えた。
想定する画面遷移ケース
SPA外のページからリンクでSPAに遷移して、その後SPA内で画面遷移することを想定する。
0. SPA外のページ
↓
1. SPAルートページ
↓
2. SPA第2階層ページ
↓
3. SPA第3階層ページ
3まで進んだあと、アプリ内の戻るボタンはもちろん、ブラウザバックでも 3 → 2 → 1 → 0 とSPAの前の画面に戻れるようにしたい。
SPAでブラウザバックを使うコードサンプル
var viewStack = [];
window.addEventListener('popstate', (e) => {
this.popView();
});
// 画面を追加する
function push(view) {
if (0 < this.viewStack.length) {
history.pushState(null, null, view.location);
}
this.viewStack.push(view);
// TODO viewを画面に表示する
}
// 前の画面に戻る
function pop() {
history.back();
}
function popView() {
if (this.viewStack.length === 1) {
return;
}
this.viewStack.pop();
// TODO 一つ前のviewを画面に表示する
}
push
の引数のviewは画面を表すインスタンスで、HTMLのelementとURLになるlocation
を持っている想定。
画面の表示を切り替える処理などは省略して TODO にしている。
ポイントと解説
window.addEventListener
window.addEventListener('popstate', (e) => {
this.popView();
});
addEventListener
の最初の引数を'popstate'
にすると、ブラウザバックのイベントをフックすることができる。
このコードによりブラウザバックが押されたら popView()
が呼ばれ、SPA内で一つ前の画面に戻ることができる。
ブラウザバックした画面がSPAのルートページなら、ブラウザが勝手に前画面に戻してくれるのでSPA側で何もする必要はない。
ルート画面でない場合はブラウザバックしてもURLが変わるだけで、ブラウザは画面を切り替えないので、popView()
関数でSPA内のページバックを行う。
history.pushState
if (0 < this.viewStack.length) {
history.pushState(null, null, view.location);
}
history.pushState
で履歴を追加できるので、SPAで画面を追加するときにpushState
を呼び出してやる。
3つ目の引数はURLのパスで、指定した値はブラウザのアドレスバーに反映される。
ここでのポイントは if (0 < this.viewStack.length)
で、ルート画面を表示するときは pushState
を呼ばないことだ。
外部ページからSPAのルート画面に遷移するときは既にブラウザによって履歴が追加されているので pushState
を実行すると履歴が2重に追加されてしまう。
history.back
// 前の画面に戻る
function pop() {
history.back();
}
SPA内の「戻る」ボタンで前の画面に戻る場合、history.back()
を呼び出せば addEventListener
で登録したコールバックが実行され、前の画面に戻ることができる。
おわりに
Chrome、Safariで思ったとおりに動くことは確認したが、本番プロダクトで試したことのない実装なので、何か問題があれば教えて欲しい。