もどる TOP

[JavaScript] history.pushStateとpopstateイベントを試してみる

 やほーーキリウ君げんき?? げんきならいいや。筆者は急にSPAのことを調べていて、下記のスライドを見てて初めてhistory.pushState()の存在を知ってよ、

まだ DOM 操作で消耗してるの?

 これって、高度な感じのWebサービスを使っていると、ページがリロードされてないのにURLがいつの間にか変わっていたり、なのになぜかブラウザバックで適切な箇所に戻れたりするあれのこと?

 ページ遷移しなくてもJavaScriptのhistory.pushState()をつかえばブラウザの「戻る」の履歴を増やせるというのと、実際に「戻る」が押されるとpopstateイベントが発火する仕組みを使って、そういうことができるみたい。

 ググってもよくわからなかったから、リファレンスを読みながらサンプルを書いてみた (下記リンク)。

 ページ上部のボタンを押すとURLが変化して見え、ブラウザバックで前に選択していたページに戻れる。実際にはpage1とかいうURLのページなど存在しないので、リロードすると404になっちゃうけど、マジメにやるならリロードとかも別の方法で制御するのかな。とにかくhistory.pushState()の第1引数に、そのページの足掛かりになる情報を突っ込めばいいのか?

サンプルページ

html
<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8" />
	<link rel="stylesheet" href="style.css" />
	<script src="script.js"></script>
	<title>pushState / popState サンプル</title>
</head>
<body>
	<div class="menu">
		<div id="go-to-page1" class="menu-item">goto1</div>
		<div id="go-to-page2" class="menu-item">goto2</div>
		<div id="go-to-page3" class="menu-item">goto3</div>
	</div>
	<div id="start" class="main">↑のボタンを押すとリロードしないのにページとURLが変わるし、ブラウザバックで戻れる</div>
	<div id="page1" class="main">ページ1!</div>
	<div id="page2" class="main">ページ2!!</div>
	<div id="page3" class="main">ページ3!!!</div>
</body>
</html>
javascript
/**
 * 最初のロード時の処理
 */
window.addEventListener('load', function() {
	setMenuClickEvent(1);
	setMenuClickEvent(2);
	setMenuClickEvent(3);
	init();
	
	function setMenuClickEvent(page) {
		document.getElementById('go-to-page' + page).addEventListener('click', function() {
			showPage(page, true);
		});
	}
});

/**
 * ブラウザの「戻る」「進む」が押されたときの処理
 */
window.addEventListener('popstate', function(event) {
	// event.stateにpushStateの第1引数が渡ってくる
	console.log('pop', event.state);
	if (event.state === null) { // セットされていなければnull
		init();
	} else {
		showPage(event.state.page, false);
	}
});

/**
 * ページの内容をリセットする
 */
function init() {
	deselectAllMenuItem();
	hideAllPages();
	document.getElementById('start').style.display = 'block';
}

/**
 * 指定のページを表示する
 * @param page 遷移先のページ番号
 * @param push trueならブラウザの履歴に追加
 */
function showPage(page, push) {
	// メニューの見た目切り替え
	deselectAllMenuItem();
	var menuItem = document.getElementById('go-to-page' + page);
	if (menuItem !== null) {
		menuItem.classList.add('selected');
	}
	
	// ページ切り替え
	hideAllPages();
	var elem = document.getElementById('page' + page);
	if (elem !== null) {
		elem.style.display = 'block';
		
		// ブラウザの履歴に追加
		if (push) {
			var stateObj = {
				page: page
			};
			var title = 'ページ' + page;
			var url = 'page' + page;
			
			// 第1引数:「戻る」「進む」が押されたときに受け取りたい情報
			// 第2引数:ページタイトル (現在は未使用らしい)
			// 第3引数:ページURL (未設定可)
			history.pushState(stateObj, title, url);
			
			console.log('push', stateObj);
		}
	}
}

function deselectAllMenuItem() {
	var menuItems = document.getElementsByClassName('selected');
	Array.prototype.forEach.call(menuItems, function(item) {
		item.classList.remove('selected');
	});
}

function hideAllPages() {
	var elems = document.getElementsByClassName('main');
	Array.prototype.forEach.call(elems, function(elem) {
		elem.style.display = 'none';
	});
}
css
.menu {
	display: flex;
	margin-bottom: 9px;
}
.menu-item {
	margin-right: 9px;
	padding: 4px 8px;
	border-radius: 4px;
	background-color: rgba(230, 230, 230, 1);
	cursor: pointer;
}
.menu-item.selected {
	font-weight: bold;
	background-color: rgba(180, 210, 255, 1);
}
.main {
	display: none;
	padding: 6px 8px;
	border-radius: 4px;
	background-color: rgba(180, 210, 255, 1);
}

 ちなみに、PCのローカルのファイル上でこういうサンプル動かそうとしたら、下記のエラーが出てダメだった。ここでは、サンプルのファイルをブラウザで表示したときのURLがfile:///C:/foo/index.htmlで、hitsory.pushState()の第3引数にbarを指定した場合。

Failed to execute 'pushState' on 'History': A history state object with URL 'file:///C:/foo/bar' cannot be created in a document with origin 'null' and URL 'file:///C:/foo/index.html'

 どうも「別のドメインのサイトに飛ばそうとするとルール違反で怒られる」的な件と関係があるみたいなんだけど、ダメなんですね。ローカルのhttpサーバー上で試したらちゃんと動いたから、ダメらしい。

サイドバーを表示する
ブログ
ShortCircuit
ShortCircuit
花火大会
天使
去る512時間前、キリウ君は折れてない千歳飴を渡してきて、ぼくが折るよう仕向けた。1024時間前、彼はこの世のものではないハッシュアルゴリズムでひとりブロックチェーンを始めていた。