knowledge base

マークアップ/フロントエンドエンジニアのWEB制作における備忘録です。平日はWEB屋、休日は社会人劇団の主宰・劇作家をしています。

ページ離脱防止機能を実装する

beforeunloadイベントを利用して実装

先日、とあるCMSの管理画面にて、記事入力途中・画像入力途中でのページ離脱防止機能の実装を行いました。

要件としては、Google Chromeのみで、離脱時にアラートを表示するというものでした。

ページ遷移前に発火するbeforeunloadというイベントを利用した実装を行なったのですが、僕自身このイベントに真正面から向かい合って実装に取り組んだことがなかったので、ところどころ小さな落とし穴があったため、その備忘録としてこの記事を書かせていただきます。

ちなみに、beforeunloadイベントの詳細はこちらを参照ください。

developer.mozilla.org

テストは必ず入力フィールドのあるページで

とても基本的な落とし穴ですがテストは必ず入力フィールドのあるページで行なってください。

Google Chrome公式でもRequire user gesture for beforeunload dialogsと記載されています。

文字入力や画面になんらかのアクションがないと、離脱防止は動きません。開いて何もせずに「戻る」を押してしまうと、何も起きずに離脱してしまいます。

https://donow.jp/skillup/?p=3271

www.chromestatus.com

僕はこれに気づかず、入力フィールドのないページでなんどもブラウザバックボタンを押しては、何も起こらないことに首をひねってしまいました。

window.onbeforeunloadイベントプロパティに関数を登録

イベントハンドリングにはaddEventListenerメソッドやonメソッド(jQuery)などの方法がありますが、後述する理由によりwindow.onbeforeunloadイベントプロパティに関数を登録する方法を採用しています。

Chromeとそれ以外のブラウザとで若干方法が異なりますが、必ずreturnをするかe.returnValueを指定してください。

同時に指定することも可能ですが、とにかくreturnをしないと離脱防止は動きません。

window.onbeforeunload = function(e){
    return "このページを離れますか?"; // Google Chrome以外
    e.returnValue = "このページを離れますか?"; // Google Chrome
}

また、Google Chromeのみ、カスタムメッセージ(つまりreturnValueやreturnに渡している文字列)は設定できず、ブラウザデフォルトのメッセージしか表示ができない仕様になっているそうです。

この件についてはこちらの記事をご参照ください。

qiita.com

フォームのsubmit時は無効にする

beforeunloadイベントはすべてのページ遷移時に発火するので、当然ながらフォームのsubmit時も発火します。

ユーザビリティのため、フォームのsubmit時は無効にするのが好ましいでしょう。

方法としてはフォームのsubmit時にbeforeunloadイベントとそれに対応する関数との紐づけを削除するのですが、removeEventListenerメソッドでもoffメソッド(jQuery)でもその紐づけを切ることができませんでした。

そこで、window.onbeforeunloadプロパティにnullを代入することで対応しました。

$('form').on('submit', function(e){
    e.preventDefault();
    window.onbeforeunload = null;  // 関数を削除
    var $this = $(this);
    var isPassed = confirm('記事を登録しますか?');
    if (isPassed) $this.submit();
});

記事の登録時には確認ダイアログを表示してほしいとの要件であったため、このような実装になっています。

これらをまとめると、以下のようになります。

window.onbeforeunload = function(e){
    return "このページを離れますか?"; // Google Chrome以外
    e.returnValue = "このページを離れますか?"; // Google Chrome
}

$('form').on('submit', function(e){
    e.preventDefault();
    window.onbeforeunload = null;  // 関数を削除
    var $this = $(this);
    var isPassed = confirm('記事を登録しますか?');
    if (isPassed) $this.submit();
});