Safariでブラウザバックすると正しく動作しない場合の対処方法を紹介します。
先に結論です。SafariでブラウザバックしたときだけJavaScriptの初期化やフォームの状態がおかしくなるなら、原因はbfcache(back-forward cache)である可能性が高いです。対策はpageshowイベントでevent.persistedがtrueのときに再初期化(またはlocation.reload())を行うことです。loadやDOMContentLoadedはbfcacheからの復元時には発火しないため、そこに初期化を書いていても動きません。
Safariでブラウザバックすると正しく動作しない原因
いろいろ原因は考えられますが、ページ遷移したときにSafariだけおかしくなる場合、bfcacheが原因の可能性があります。
典型的な症状は次のとおりです。
- 戻った直後にローディング表示やメニューの開閉状態が遷移前のまま残る
- ログアウト後に戻るとログイン中の画面が表示される
- 送信済みのフォームに入力値が残ったまま表示される
- 戻ったときにスクリプトが再実行されず、ボタンが反応しない
どれも「ページが再読み込みされず、離脱した瞬間の状態がそのまま復元される」ために起こります。
bfcacheとは
ブラウザには、画像などの重たいものを一時的に保存しておくキャッシュ機能があります。
サーバー側で変更しても見た目が変わらない場合、このブラウザのキャッシュが影響している場合があります。
bfcache(back-forward cache)はそれとは別で、ページ全体(DOMとJavaScriptの実行状態)をメモリ上に保持しておき、戻る・進むの操作で瞬時に復元する仕組みです。
一度訪問したページから戻って再び前に進んでも、ページを読み込み直さずにキャッシュから復元されます。
以前はSafariの挙動として語られることが多かったのですが、現在はChromeとFirefoxにも同じ仕組みがあります。ただしSafariは早くから積極的に使ってきたため、Safariだけ症状が目立つケースが今でもあります。
このbfcacheが原因で、ブラウザバックしたときに思わぬ状態になっていることがあります。
bfcacheの対策
// 強制リロード
window.addEventListener('pageshow', function (event) {
if (event.persisted) {
// bfcache発動時の処理
window.location.reload();
}
});
addEventListenerのpageshowというイベントがあります。
- ブラウザの進む、戻るボタンを利用してこのページに戻ったとき
- 最初にページを読み込んだとき
- 同じウィンドウまたはタブの中で、他のページからそのページへ移動してきたとき
- モバイルOSで凍結されたページを復元したとき
このpageshowイベントで、ブラウザバックしたときのイベントを取得して、強制的にリロードさせます。
bfcacheから復元されたかどうかの判定は、event.persistedで可能です。
bfcacheが有効のとき、event.persistedがtrueになるので、そのときに強制リロードさせます。
addEventListenerの基本はaddEventListenerの基本とさまざまな使い方で解説しています。
リロードせずに状態だけ戻す
毎回リロードすると、せっかくの高速な復元が無駄になります。表示状態だけが問題なら、リロードの代わりに必要な初期化だけをやり直すほうが体験を損ないません。
function init() {
// メニューを閉じる、ローディングを消す、最新データを取り直すなど
document.querySelector('.menu').classList.remove('is-open');
}
window.addEventListener('pageshow', function (event) {
if (event.persisted) {
init();
}
});
ログイン状態のように「復元されては困る」情報を含むページは、復元後にfetchで最新の状態を取り直すか、上のようにリロードしてください。
unloadを使っているとbfcacheの対象外になる
逆に「bfcacheが効かない」場合、ページにunloadイベントのリスナーがあることが原因になりやすいです。多くのブラウザはunloadを登録したページをbfcacheの対象から外します。離脱時の処理はpagehideに書き換えます。
window.addEventListener('pagehide', function (event) {
// event.persistedがtrueならbfcacheへ入る
// 計測の送信などはここで行う
});
このほか、Cache-Control: no-storeを返すページや、離脱時点で開いたままの接続(WebSocketなど)があるページも、ブラウザによっては復元の対象外になります。ChromeのDevToolsでは「Application」パネルの「Back/forward cache」から、対象外になった理由を確認できます。