読者です 読者をやめる 読者になる 読者になる

dunno logs

神山生活四年目

Mobile Safari で戻るボタンを使えなくする (iOS 7)

戻るボタン対応

これ自体はいろんな 対応方法 があると思うのですが、今回は 戻らせない を目的としています。(戻っても大丈夫とか、戻るボタンを効かなくするとかではありません)

PC や Android Chrome の場合

よくある対応として以下のようなものがありますね。PC 、Android 4.1.2 Chrome ではこの対応である程度要件を満たす事ができました。

<script type="text/javascript">
  window.onunload = function() {};
  history.forward();
</script>

参考

Mobile Safari の場合

Mobile Safari に限った話しではないかもしれませんが、上記の対応では普通に戻れてしまいます。Webインスペクト機能でデバッグしてみると、どうもキャッシュ画面が使われてしまっています。onunload の対応もしているのに何故?というところなのですが、どうやら back forward cache が効いているようです。

以降では、上記の中から今回対応した内容を中心に記述します。上記はあくまで調査過程の参考リンクということで。

onpageshow イベントでの実装

back forward cache に対する対策として、よく使われているのが onpageshow イベント時にリロードするというものでした。

window.onpageshow = function(event) {
  if (event.persisted) {
    window.location.reload();
  }
};

event.persisted が重要で、これが true になった場合には、キャッシュが使われたページという事になります。この対応では、キャッシュされたページでは onload などのイベントが発火しないので、リロードすることでそれを回避しています。

つまり以下のようにすると、戻らせない対応を可能のように思えます。

window.onpageshow = function() {
  history.forward();
};

onpageshow の課題

onpageshow一度しか発火しない 事が分かりました。よって、一度は戻れない事が実現できても、さらにもう一度「戻るボタン」を押されると戻れてしまいます。

一度というのは、キャッシュされたページに対してという事であり、リロードしていれば大丈夫です。よって、以下のようにすると大丈夫なように思えます。

window.onpageshow = function(event) {
  if (event.persisted) {
    window.location.reload();
  }
};
history.forward();

ある程度は上手くいきますが、この対応の問題点は「リロードできないページがある場合にエラーとなる」事です。Rails のルーティング設定で GET を受け付けないようになっている URL をリロードした場合を想像してもらうと良いかもしれません。

popState イベントでの対応

onpageshow は一度しか発火しない、ではキャッシュされたページであっても必ず発火するイベントがあればいいわけです。

History API が実装されたブラウザであれば popState イベントが利用できます。popState は履歴の変化があった場合に発火されるため、キャッシュされたページでも大丈夫なようです。

window.onpopstate = function() {
  history.forward();
};

この対応は onpageshow と違いリロードの問題も起きないため比較的安定します。

ただ、戻る連打すると変になることがあり、やはりネットワーク環境も、そもそも性能も低いモバイル環境で history.forward での戻るボタン制御は難しいのかなと。

pushState で開いたページと同じ URL を積む

window.onpopstate = function() {
  history.pushState(); // 引数無しだと state 無しで、開いているページの URL が履歴に積まれる
};

以下に履歴がどうなっているかのイメージを書いてみました。(これでいいのか。。)

ページA [A]
onpopstate [A, A]
↓
(ページBに遷移)
↓
ページB [A, A, B]
onpopstate [A, A, B, B]
↓
(戻る) => 履歴が積まれているため戻るのは [A, A, *B, B] の * の部分。よって同じページ。
↓
ページB [A, A, B]
onpopstate [A, A, B, B]

ちょっとトリック的なのですが、戻るボタン連打にも耐えてくれたため、そこそこ良い対応方法なのではと思います。

問題点

Mobile Safari には過去の履歴に飛べる機能があるため、それを実行されるとどうにもなりません。そこが history.forward 対応との違いですね。history.forward の場合は、強制的に元のページまで遷移させてくれるので。

まとめ

今回の要件的に最終的には popState イベント時に同じURLを積むという対応を採用しました。記載したようなリスクもありますが、そこを許容すれば良いバランスではなかったかと。

逆に、戻った画面で操作できる事を目的にするのであれば、onpageshowevent.persisted == true 時にリロードするが良い対応なのかなと思いました。

onpageshow と popstate の書き方が最後まで統一できてないな。。。どうして片方だけ on が付いたし。。