ブラウザごとのlocation.hashの挙動のまとめ

今更ですが、location.hashで現在のステータスを管理するのがマイブームです。しかし、ASCII以外をぶち込むとブラウザごとに挙動が違うっぽいです。というのを調べておいて、書いておくのを忘れたので、まとめておきます。

方法

以下のアクションを起こした場合、location.hashで取得できる値がどうなるか、調べました。なお、ファイルはUTF-8で試しました。

ファイルに書いてあるリンクをクリックした場合
  • A1:#test
  • B1:#てすと
  • C1:#%E3%81%A6%E3%81%99%E3%81%A8
  • A2:#test#test
  • B2:#てすと#てすと
  • C2:#%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8
  • A3:#test%23test
  • B3:#てすと%23てすと
  • C3:#%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8

頭が#じゃないやつの結果は見えているのでやりません。

location.hashへ代入した場合

a1〜c3まで上のテストの文字列から#を削ったものと一致します。

こいつを地道にクリックした感じです。もうちょい自動化されたやつはどっかいった。

<!doctype html>
<html>
<head><meta charset="UTF-8"></head>
<body>
<p>
<a href="#test">A1</a>
<a href="#てすと">B1</a>
<a href="#%E3%81%A6%E3%81%99%E3%81%A8">C1</a>
<a href="#test#test">A2</a>
<a href="#てすと#てすと">B2</a>
<a href="#%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8">C2</a>
<a href="#test%23test">A3</a>
<a href="#てすと%23てすと">B3</a>
<a href="#%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8">C3</a>
</p>
<p>
<span id="a1">a1</span>
<span id="b1">b1</span>
<span id="c1">c1</span>
<span id="a2">a2</span>
<span id="b2">b2</span>
<span id="c2">c2</span>
<span id="a3">a3</span>
<span id="b3">b3</span>
<span id="c3">c3</span>
</p>
<p><input id="r"></p>
<script type="text/javascript">
function $(id) { return document.getElementById(id); }
$("a1").onclick = function () {
	location.hash = "test";
};
$("b1").onclick = function () {
	location.hash = "てすと";
};
$("c1").onclick = function () {
	location.hash = "%E3%81%A6%E3%81%99%E3%81%A8";
};
$("a2").onclick = function () {
	location.hash = "test#test";
};
$("b2").onclick = function () {
	location.hash = "てすと#てすと";
};
$("c2").onclick = function () {
	location.hash = "%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8";
};
$("a3").onclick = function () {
	location.hash = "test%23test";
};
$("b3").onclick = function () {
	location.hash = "てすと%23てすと";
};
$("c3").onclick = function () {
	location.hash = "%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8";
};

setInterval(function () {
	$("r").value = location.hash;
}, 1000);
</script>
</body>
</html>

結果

ブラウザ A1 B1 C1 A2 B2 C2 A3 B3 C3
IE6 #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Fx3.5 #test #てすと #てすと #test#test #てすと#てすと #てすと#てすと #test#test #てすと#てすと #てすと#てすと
Opera10 #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Safari4 #test #%E3%81%A6%E3%81%99%E3%81%A8 #%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8 #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8 #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Chrome #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8

IEOperaChromeは代入された値を返す。Firefoxは全てdecode、Safariは全てencodeされた結果を返す。

ブラウザ a1 b1 b1 a2 b2 c2 a3 b3 c3
IE6 #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Fx3.5 #test #てすと #てすと #test#test #てすと#てすと #てすと#てすと #test#test #てすと#てすと #てすと#てすと
Opera10 #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Safari4 #test #%E3%81%A6%E3%81%99%E3%81%A8 #%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #fYh%23fYh #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8 #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8
Chrome #test #てすと #%E3%81%A6%E3%81%99%E3%81%A8 #test#test #てすと#てすと #%E3%81%A6%E3%81%99%E3%81%A8#%E3%81%A6%E3%81%99%E3%81%A8 #test%23test #てすと%23てすと #%E3%81%A6%E3%81%99%E3%81%A8%23%E3%81%A6%E3%81%99%E3%81%A8

Safariはencodeしないとバグる場合がある。

まとめ

  • アンカーを付けたリンクを生成する場合は必ずencodeURIComponentでencodeして付与する。
  • location.hashに代入する場合も必ずencodeURIComponentでencodeして代入する。
  • 以上が守られたlocation.hashの値を取得する場合はFirefox以外はdecodeURIComponentでdecodeする。

と、クロスブラウザなlocation.hashの扱いが可能になる。

おまけ

jQueryライブラリにしてあったのを思い出した。

$.anchor = function (val, skip) {
	var fn = arguments.callee;
	switch (typeof val) {
		// getter
		case "undefined":
			var ret = location.hash;
			if (!ret) {
				return null;
			}
			ret = ret.replace(/^#/, "");
			return $.browser.fx ? ret : decodeURIComponent(ret);
		// callback
		case "function":
			if (!fn.tid) {
				fn.hash = location.hash;
				(function rec() {
					if (!fn.skip && fn.hash !== location.hash) {
						fn.hash = location.hash;
						$(window).trigger("changeAnchor", $.anchor());
					}
					fn.tid = setTimeout(rec, 300);
				})();
			}
			return $(window).bind("changeAnchor", val);
		// setter
		default:
			if (skip) {
				fn.skip = true;
				location.hash = encodeURIComponent(val);
				fn.hash = location.hash;
				fn.skip = false;
			}
			else {
				location.hash = encodeURIComponent(val);
			}
			return;
	}
};

// 取得
$.anchor();
// 代入
$.anchor("てすと");
$.anchor("てすと", true); // イベントに反映させない
// location.hashが変化したら、取得
$.anchor(function (evt, hash) { alert(hash + "に変わったよ") });

なんか、ページ番号とかをlocation.hashで管理している場合は、使い道があるかもしれません。バグっているかもしれないけど。