Help us understand the problem. What is going on with this article?

History APIとスマホブラウザでのハマりどころ

More than 5 years have passed since last update.

仕事でスマホ用サイトをイジっておりまして、非同期処理でのページングを
修正するに際して、History APIを触ってました。
スマホのブラウザ周りの差分で色々とハマったので、メモしておきます。

History APIとは

とりあえず復習しましょう。

概要

HTML5で追加された、履歴操作のAPI。
履歴スタックに履歴を追加(push)、現在の履歴の書換(replace)、
ブラウザバック等のアクションによる履歴移動を監視する(pop)、といったことが可能になる。
history自体は以前からあって履歴の操作は画面遷移を伴っていたが、これらのAPIの追加によって、
画面遷移を行わなくともJavaScriptからの履歴操作が可能となった。

pushState

履歴をスタックに追加する。

pushStateの例
history.pushState('test', null, '/hoge/fuga');

「test」という識別子(state)で履歴を追加、
対応するURLとして「/hoge/fuga」を設定するということになります。
履歴が追加されることにより、ブラウザ上のURLが書き換えられます。

replaceState

現在の履歴を置き換える。

replaceStateの例
history.replaceState('test', null, '/hoge/fuga');

引数はpushStateと同じですが、履歴の追加ではなく置き換えとなります。

popState

履歴のwindow.onpopstateイベントの発火により、履歴の移動を検知できます。
コールバック関数をイベントバインドしておきましょう。

onpopstateのバインド例
window.addEventListener('popstate', function (e) {
    ...コールバック処理を記述
}

history.state

pushState、replaceStateで設定したstateは、historyオブジェクト経由で取得できます。

history.stateの例
history.replaceState('hoge', null, '/hoge/fuga');
console.log(history.state); // hogeと出力

スマホブラウザでの挙動

大概のやつは普通に使えるでしょ、と軽い気持ちでやったら色々と差分があって
うぉーとなりました。
ハマった点を以下につらつらと書いておきます。
※ 手元にそれほど端末がなく、以下3機種での検証に留まってますので
参考程度の情報として捉えてください。

  • SC02-B(GALAXY S) Android 2.2.1
  • SC06-D(GALAXY S III) Android 4.1.2
  • iPhone5S iOS 7.1.2

そもそもHistory APIに未対応なAndroidがある

実機での検証はできていませんが、一部あるらしく、

  • Android2.2以降は対応
  • しかしAndroid 3.x, Android 4.0では未対応
  • Android4.1以降は対応

ということになってるようです。
しかし、以下のソースだと4.1も未対応ということのようで、
http://caniuse.com/#search=history
実際のところどこまでが対応済みなのかは、はっきりとは分かってません。
※ 端末固有の差分とかもありそうだし。

History APIの使用ができるか否かを判定し、処理を切り分けておくのが
無難ですね。

history.stateが使えないブラウザがいる

history.pushStateはできるけど、history.stateは使用できない(undefinedになる)という。
Android2.2の標準ブラウザで気づいたので、以下の様なテストコードを用いて検証してみました。

テストコード
if(history.pushState) {
    alert("history api can use");
} else {
    alert("history api can not use");
}
if(history.state === undefined) {
    alert("history.state can not use");
} else {
    alert("history.state can use");
}

結果は以下のとおり。

端末 OSバージョン ブラウザ history.pushState history.state
SC02-B Android 2.2.1 Android標準 使用可 使用不可
SC06-D Android 4.1.2 Android標準 使用可 使用不可
SC06-D Android 4.1.2 Chrome(ver.40.0.2214.109) 使用可 使用可
iPhone5S iOS 7.1.2 Safari 使用可 使用不可
iPhone5S iOS 7.1.2 Chrome(ver.40.0.2214.69) 使用可 使用不可

ブラウザ毎に、この辺りは差分が生じているようです。
http://stackoverflow.com/questions/10941553/pushstate-popstate-safari-5-not-returning-window-history-state

history.stateが使用できないと、片落ち感が否めないですね。。

画面ロード時にonpopstateが発火するブラウザがいる

onloadと同様のタイミングで発火する場合があることに気づき、以下の実機で再現確認しました。

端末 OSバージョン ブラウザ
SC06-D Android 4.1.2 Chrome(ver.40.0.2214.109)
iPhone5S iOS 7.1.2 Safari
iPhone5S iOS 7.1.2 Chrome(ver.40.0.2214.69)

この辺りの話ですかね。
http://lists.w3.org/Archives/Public/public-html-bugzilla/2010Oct/3102.html

初回画面ロード時はstateが設定されていないので、とりあえず以下の様な制御で
コールバック処理の実行有無は切替できそう。

popstateコールバック制御の例
window.addEventListener('popstate',function(e){
    if (e.state) {
        ...コールバック処理を記述
    }
});

pushStateしたURLがlocationオブジェクトに同期されないブラウザがいる

テストコード
history.replaceState('hoge', null, '/hoge/fuga?hogehoge=fugafuga');
console.log(location.search); // ?hogehoge=fugafugaと出力されてほしい

これで、「?hogehoge=fugafuga」と取れないブラウザがあります。
location.searchだけではなくて、location.hrefとかもダメですね。
以下の実機で再現確認しています。

端末 OSバージョン ブラウザ
SC02-B Android 2.2.1 Android標準

どうもAndroidのバグっぽいんですよね、これ。
https://code.google.com/p/android/issues/detail?id=17471

対象を判定するには、pushStateする前のlocation情報(hrefとか)と
pushStateした後のlocation情報を突き合わせる、とかしか方法が思いつかず。
面倒ですね。

まとめ

だらだら書いてしまいましたが、自分が実装した範囲では

HistoryAPI使用判定例
history.pushState && history.state !== undefined;

これが真を返すブラウザは、History APIを使った制御をする、
という切り分けでいくことにしました。
locationオブジェクトが同期されない件は少々気になるところですが、
切り分け基準が明確にならないので、とりあえず様子見ということで。

今回は地道に実装しましたが、要件に応じてjquery-pjaxHistory.jsのようなライブラリを導入した方が諸々楽できそうな気がしました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away