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 が効いているようです。
- bfcache について覚えて帰ってもらいます。
- Safari Browser Back button issue
- Using Firefox 1.5 caching
- iPhone Safari の「戻る」「進む」を検知する
- iOS7のMobile Safariでブラウザバック、フォワードした時にキャッシュを回避してリクエストさせる方法
- Problems with Page Cache in iOS 5 Safari when navigating back / unload event not fired
- Mobile SafariやAndroid標準ブラウザでhistory.backした際にloadイベントを走らせる方法
- レスポンスにキャッシュしないように指定してもSafariだとキャッシュしてしまう問題の解消
- Mobile Safari back button
- iPhone Safariの戻るボタンの挙動
以降では、上記の中から今回対応した内容を中心に記述します。上記はあくまで調査過程の参考リンクということで。
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を積むという対応を採用しました。記載したようなリスクもありますが、そこを許容すれば良いバランスではなかったかと。
逆に、戻った画面で操作できる事を目的にするのであれば、onpageshow
の event.persisted == true
時にリロードするが良い対応なのかなと思いました。
onpageshow と popstate の書き方が最後まで統一できてないな。。。どうして片方だけ on が付いたし。。