ブラウザの「戻るボタン」を押した時、ポップアップを表示させる※離脱防止【javascript】

javascript

ブラウザの「戻る」ボタンをクリックした時にポップアップが表示されるようにしました。
主に入力フォームでの離脱防止対策です。

完成したポップアップを確認する(デモ)

動作デモ

実際の動作はこちらで確認できます。遷移先のページでブラウザの「戻る」ボタンをクリックしてください。

ソースコードを確認

ポップアップ表示に必要な部分を抜き出したコードがこちらです。

コード全体

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta id="viewport" name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1" />
    <meta charset="UTF-8">
    <title>ブラウザバックでポップアップ表示サンプル | えむ家のメモ帳</title>
    <!-- anonymousCDN -->
    <link href="<ディレクトリ>/fontawesome/css/all.css" rel="stylesheet">
    <script
	src="https://code.jquery.com/jquery-3.4.1.min.js"
	integrity="<>"
	crossorigin="anonymous"></script>
    <!-- /anonymousCDN -->
    <style>
        /* ポップアップ */
        .popup-area {
            display:none;
            width:80vw;
            background:#fff;
            padding:20px;
            position: fixed;
            z-index: 9999;   
            top: 50%;
            left: 50%;
            transform: translateY(-50%) translateX(-50%);
            text-align: center;
            border-radius:8px;
            box-shadow: 0 0 4px 4px rgba(0,0,0,0.4);
        }
        @media screen and (min-width: 980px) {
                .popup-area {
                width:auto;
                max-width: 460px;
            }
        }
        .popup-area button {
            margin:30px auto 0;
        }
        .popup-area .icon{
            font-size:3em;
            color:red;
        }
        /* 背景色 */
        .cover-eml {
            background:rgba(0,0,0,0.3);
            width: 100%;
            height: 100%;
            position: fixed;
            z-index: 999;   
            top:0;
            right:0;
            bottom:0;
            left:0;
        }
    </style>
</head>

<body>
    <!-- ポップアップ -->
    <div class="popup-area">
        <i class="fas fa-exclamation-triangle icon"></i>
        <p>ブラウザの「戻る」が実行されました。</p>
        <button class="close-btn">閉じる</button>
    </div>
    <!-- 古いブラウザ対応 -->
    <script src="<ディレクトリ>/jquery.history.js"></script>

    <script>
        "use strict";
            //ポップアップ非表示の判定
            let popAppend = false;

            //履歴の追加
            let hash = location.hash;
            if(hash != '#back') {
                history.pushState(null,null,location.href);
                history.replaceState(null,null,'#back');
            }

            //設定したハッシュタグが消えたら実行
            window.addEventListener('popstate',(e) => {
                if(location.hash != "#back" && popAppend === false) {
                $('.popup-area').fadeIn();
                $('body').append($("<div>", {class: 'cover-eml'}));
                popAppend = true;

                if(popAppend) {
                    $('body').on('click', '.cover-eml' , function() {
                        deleteElm();
                        popAppend = false;
                    });
                }
            }
        });

        //バナー削除
        $('body,.close-btn').on('click', '.close-btn', function() {
            deleteElm();
            popAppend = false;
        });
        function deleteElm() {
            $('.cover-eml').fadeOut();
            $('.cover-eml').remove();
            $('.popup-area').fadeOut();
        }

            
    </script>
</body>
</html>

ポップアップはこんなカンジのデザインにしました。Bootstrapなどのフレームワークを使っても良いかも。

ブラウザの戻るで表示されるポップアップ

アラートのアイコンはFont AwesomeのWebアイコンです。使用するにはメールアドレスの登録が必要でした。外部で読み込むファイルは、コード内の<ディレクトリ>部分を環境に合わせて変更してください。

ポイントとなるコードをメモしておきます。

HTML

ポップアップのHTMLを事前に記述しておきます。

<div class="popup-area">
    <i class="fas fa-exclamation-triangle icon"></i>
    <p>ブラウザの「戻る」が実行されました。</p>
    <button class="close-btn">閉じる</button>
</div>

CSS

ポップアップは最初、display:noneで非表示にしておきます。「戻る」ボタンがクリックされたら表示させます。今回、表示された時の背景色を半透明の黒にしました。背景用のDOMはjavascriptで挿入します。

/* ポップアップ */
.popup-area {
    display:none;
    width:80vw;
    background:#fff;
    padding:20px;
    position: fixed;
    z-index: 9999;   
    top: 50%;
    left: 50%;
    transform: translateY(-50%) translateX(-50%);
    text-align: center;
    border-radius:8px;
    box-shadow: 0 0 4px 4px rgba(0,0,0,0.4);
}
@media screen and (min-width: 980px) {
        .popup-area {
        width:auto;
        max-width: 460px;
    }
}
.popup-area button {
    margin:30px auto 0;
}
.popup-area .icon{
    font-size:3em;
    color:red;
}
/* 背景色 */
.cover-eml {
    background:rgba(0,0,0,0.3);
    width: 100%;
    height: 100%;
    position: fixed;
    z-index: 999;   
    top:0;
    right:0;
    bottom:0;
    left:0;
}

javascript

"use strict";
    //ポップアップ非表示の判定
    let popAppend = false;

    //履歴の追加
    let hash = location.hash;
    if(hash != '#back') {
        history.pushState(null,null,location.href);
        history.replaceState(null,null,'#back');
    }

    //設定したハッシュタグが消えたら実行
    window.addEventListener('popstate',(e) => {
        if(location.hash != "#back" && popAppend === false) {
        $('.popup-area').fadeIn();
        $('body').append($("<div>", {class: 'cover-eml'}));
        popAppend = true;

        if(popAppend) {
            $('body').on('click', '.cover-eml' , function() {
                deleteElm();
                popAppend = false;
            });
        }
    }
});

//バナー削除
$('body,.close-btn').on('click', '.close-btn', function() {
    deleteElm();
    popAppend = false;
});
function deleteElm() {
    $('.cover-eml').fadeOut();
    $('.cover-eml').remove();
    $('.popup-area').fadeOut();
}

history.pushState(statetitleurl)で履歴を追加

ページが読み込まれた時、pushState()メソッドでアクセスしたURLを履歴に追加します。
この方法で履歴を追加した場合、通信は発生しません。
履歴が追加されたら、現在のURLを置き換えてハッシュ#backを付与します。ハッシュ名はなんでもOKです。

//履歴の追加
let hash = location.hash;
if(hash != '#back') {
    history.pushState(null,null,location.href);
    history.replaceState(null,null,'#back');
}

URLを確認するとハッシュ#backが付与されています。ブラウザの「戻る」ボタンを長押しすると、今表示しているページが履歴に追加されています。

現在表示されているページが履歴に追加されてる

popstateイベントで戻るボタンをクリックしたか判定する

履歴を追加された状態でブラウザの「戻る」をクリックすると、ハッシュがない同じページに遷移します。追加したハッシュの有無でポップアップを表示するか判定します。
ブラウザの「戻る」ボタンがクリックされたかを判定するにはpopstateイベントを使います。
※2重表示を避ける為、変数popAppendでポップアップの非表示も一緒に判定してます。

//設定したハッシュタグが消えたら実行
window.addEventListener('popstate',(e) => {
    if(location.hash != "#back" && popAppend === false) {
    $('.popup-area').fadeIn();
    $('body').append($("<div>", {class: 'cover-eml'}));
    popAppend = true;

    if(popAppend) {
        $('body').on('click', '.cover-eml' , function() {
            deleteElm();
            popAppend = false;
            });
        }
    }
});

popstateイベントは、ブラウザの「戻る」ボタンをクリックしたら発火するイベントです。

イベントが発火したら、非表示にしていたポップアップを表示して背景色用のDOMcover-emlを追加します。
これでブラウザの「戻る」ボタンをクリックしたら表示されるポップアップが完成しました!

動作環境について

完成したポップアップですが、すべての環境で同じように動作する訳ではありません。
PCはMacのChromeとFirefox、スマホはiOSのsafari、Androidのchrome、firefoxで動作確認しました。

対応していない古いブラウザ

そもそもpopstateイベントはHTML5で追加された新しいAPIです。古いブラウザは対応していません。もしIEなどの未対応ブラウザも対象とするなら、機能を補完できるhistory.jsを読み込んでおきます。

GitHub - browserstate/history.js: History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype. For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it will revert back to using the old onhashchange functionality.
History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles,...

コードはこの部分です。GitHubからダウンロードしたファイルをサーバーにアップして読み込ませるだけで動作しました。

<!-- 古いブラウザ対応 -->
<script src="<ディレクトリ>/jquery.history.js"></script>

最近は古いブラウザに対応する必要はあんまりないと思いますが。。

Chromeの挙動について

Chromeは挙動がちょっと不安定です。
最初にアクセスした場合はちゃんとポップアップが表示されますが、2回目だと表示されません。
iOS以外のChromeに発生しました。

調べたところ、セキュリティの関係か2回目のpopstateイベントの実行が無視されるようです。
その場合、フォームの内容を変更するとポップアップが表示されます。ページに何かしら変更があればpopstateイベントが発火するようです。

フォームに値を入力すれば2回目のアクセスでもポップアップが表示される

参考サイト

WindowEventHandlers.onpopstate - Web API | MDN
WindowEventHandlers ミックスインの onpopstate プロパティは、ウィンドウの popstate (en-US) イベントを処理するための event handler です。

まとめ

今回、ECサイトの入力フォームを想定しています。
ページを読み込んだ時に履歴を追加しているので、リロードすると挙動がおかしくなります。
そういった動作が想定される場合、何かしら処理を追加したほうが良さそうです。

ページ遷移をフロント側で制御するのは限界がありそうだと感じました。。
良い方法があれば教えてください。。