history.pushStateとpopstateを試してみる
今時のWebサービス使ってると、ページがリロードされてないのにURLがいつの間にか変わってたり、なのになぜかブラウザバックで適切な箇所に戻れたりするアレ。
SPAのこと調べてて、次のスライド見てました。
それで、初めて history.pushState()
の存在を知りました。これ使うと、ページ遷移しなくてもブラウザの「戻る」の履歴を増やせるらしいです。そして実際に「戻る」が押されたときには、popstate
イベントを使ってページの状態をハンドリングできるみたいです。
ブラウザの履歴を操作する - ウェブデベロッパーガイド | MDN
↑のページ読みながら、サンプル書いてみました。
See the Pen pushstate-and-popstate by napoporitataso (@napoporitataso) on CodePen.
ここではあらかじめHTMLに全ての画面の内容を書いておき、リンクが踏まれた時に href
を見て、画面に対応する要素の表示/非表示を切り替えているだけです。state
には画面の識別子を持たせており、どの画面を表示するかはそれで判断しています。
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="noarchive" />
<title>pushState / popState サンプル</title>
</head>
<body>
<p>現在のURL: <span id="url"></span></p>
<div data-screen="start">
<h1>スタート</h1>
<p>リンクを踏むと、リロードなしでページの内容とURLが切り替わります。</p>
<p>ブラウザで「戻る」「進む」もできます。</p>
<a href="#p1">次→</a>
</div>
<div data-screen="p1">
<h1>その1</h1>
<a href="#start">←前</a> <a href="#p2">次→</a>
</div>
<div data-screen="p2">
<h1>その2</h1>
<a href="#p1">←前</a> <a href="#p3">次→</a>
</div>
<div data-screen="p3">
<h1>その3</h1>
<a href="#p2">←前</a>
</div>
<script src="script.js"></script>
</body>
</html>
'use strict';
/**
* 画面を表示する
* @param {string} screen 画面の識別子
*/
const showScreen = (screen) => {
// 遷移先の画面に対応する要素だけを表示し、それ以外は非表示に
document.querySelectorAll('[data-screen]').forEach((elem) => {
if (elem.dataset.screen === screen) {
elem.style.display = 'block';
} else {
elem.style.display = 'none';
}
});
};
/**
* 現在のURLを表示する
*/
const showUrl = () => {
document.querySelector('#url').textContent = document.location.toString();
};
// hrefが#から始まるaタグがクリックされたとき
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener('click', (e) => {
e.preventDefault(); // aタグ本来の画面遷移をキャンセル
// 対象の画面を表示
const screen = e.currentTarget.hash.substr(1);
showScreen(screen);
// https://developer.mozilla.org/ja/docs/Web/API/History/pushState
// @param {any} 「戻る」「進む」が押されたときに受け取りたいデータ (state)
// @param {string} 空文字固定
// @param {string} 遷移先のURL
history.pushState({ screen }, '', `./${screen}`);
showUrl();
});
});
// ブラウザの「戻る」「進む」が押されたとき
window.addEventListener('popstate', (e) => {
// e.stateにはpushStateの第1引数にセットした値が入ってる
// セットされていなければ最初の画面を表示、セットされていればその画面を表示
if (e.state === null) {
showScreen('start');
} else {
showScreen(e.state.screen);
}
showUrl();
});
// 初期表示
showScreen('start');
showUrl();
Note
なお、これだけだとブラウザで画面をリロードしたりURLを直接指定してアクセスした時に、URLに対応するファイルが実在しないので画面を表示できません。
そのためサーバー側で .htaccess
などを使用して全てのアクセスを index.html
に集めるといった、フォールバック的な動きをさせる必要があります。
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.html [L]