openメソッドをモーダルもどきにする

はじめに

先日以下をメモりました。 ただ改善すべき箇所もありめもっておきます。

対応したいこと

1.親ウィンドウをクリックできないようにしたい。 2.子ウィンドウを常に親ウィンドウの前に出したい。 3.親画面遷移時、子ウィンドウを閉じたい

1の例

一般的に「モーダルダイアログ」としての挙動を実現する必要があります。しかし、window.open() で開いたウィンドウは「モーダル」にはならず、親ウィンドウの操作はブロックされません。

対応例

  • 子ウィンドウが開いた際に、親ウィンドウ全体を覆う半透明のオーバーレイを表示し、親ウィンドウでのクリック操作を防ぎます。
  • 子ウィンドウが閉じられるとオーバーレイを自動で解除します。

これにより、親ウィンドウが「モーダル」な動作をするように見せかけることができます。

2の例

・focusで制御 ブラウザ環境では、子ウィンドウ(window.open()で開いたウィンドウ)を常に親ウィンドウの前に表示し続ける方法は、標準的なJavaScriptAPIでは提供されていません。これは、ブラウザのセキュリティやプライバシー保護のためです。

ただし、以下の方法で親ウィンドウをフォーカスしたときに、子ウィンドウを再度前に表示する動作を模倣することができます。

focus() メソッドを使用する

親ウィンドウがフォーカスされるたびに、子ウィンドウにフォーカスを戻すために focus() メソッドを使用します。次のように親ウィンドウの focus イベントを監視して、子ウィンドウをフォーカスするように設定できます。

1と2の例を以下に盛り込んでいます。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Window Open Example</title>
    <style>
      /* オーバーレイのスタイル */
      #overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.5); /* 半透明の背景 */
        z-index: 9999; /* 最前面に表示 */
        display: none; /* 最初は非表示 */
      }
    </style>
</head>
<body>

<h1>親ウィンドウ</h1>
<input type="text" id="name" name="name" required minlength="4" maxlength="8" size="10" />
<button id="openDialogBtn">モーダルを開く</button>
<p id="result">選択された結果: なし</p>

<div id="overlay"></div> <!-- オーバーレイの要素 -->

<script>
function openModalDialog(url) {
    const width = 500;
    const height = 400;
    const left = (window.screen.width - width) / 2;
    const top = (window.screen.height - height) / 2;

    return new Promise((resolve, reject) => {
        // ウィンドウを開く
        let dialogWindow = window.open(
            url, 
            'modalDialog', 
            `width=${width},height=${height},left=${left},top=${top}`
        );

        // ウィンドウがブロックされている場合
        if (!dialogWindow) {
            reject(new Error('ポップアップがブロックされました'));
            return;
        }

        // 親ウィンドウがフォーカスされたとき、子ウィンドウにフォーカスを戻す
        window.addEventListener('focus', () => {
            if (dialogWindow && !dialogWindow.closed) {
                dialogWindow.focus(); // 子ウィンドウにフォーカスを戻す
            }
        });

        // 親ウィンドウをクリックできないようにオーバーレイを表示
        document.getElementById('overlay').style.display = 'block';

        // メッセージイベントのリスナーを追加(子ウィンドウからのメッセージを受信)
        window.addEventListener('message', function(event) {
            // オリジンを確認して、信頼できるソースからのメッセージのみ処理する
            if (event.origin !== window.location.origin) {
                return;
            }

            // オーバーレイを非表示にする
            document.getElementById('overlay').style.display = 'none';

            // メッセージを解決する
            resolve(event.data);
        });

        // ウィンドウが閉じられたことを定期的にチェック
        let interval = setInterval(() => {
            if (dialogWindow && dialogWindow.closed) {
                clearInterval(interval);
                // オーバーレイを非表示にする
                document.getElementById('overlay').style.display = 'none';
            }
        }, 500);
    });
}

    // 親ウィンドウが遷移する際に子ウィンドウを閉じる
    window.addEventListener('beforeunload', () => {
        if (dialogWindow && !dialogWindow.closed) {
            dialogWindow.close();  // 子ウィンドウを閉じる
        }
    });

// ボタンのクリックイベントでモーダルを開く
document.getElementById('openDialogBtn').addEventListener('click', () => {
    openModalDialog('child.html')
        .then((result) => {
            document.getElementById('result').innerText = `選択された結果: ${result}`;
        })
        .catch((error) => {
            alert(error.message);
        });
});


</script>

</body>
</html>

3の例

親画面遷移時、子ウィンドウを閉じる

親ウィンドウがページ遷移するタイミングで子ウィンドウを閉じる方法は、親ウィンドウの beforeunload イベントを使用するのが一般的です。beforeunload イベントは、親ウィンドウがリロードまたは他のページに移動する前に発生します。このイベントの中で子ウィンドウを閉じることで、親ウィンドウのページ遷移時に子ウィンドウを自動的に閉じることができます。

以下にその方法を示します。

親ウィンドウ (index.html) の修正

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Window Open Example</title>
</head>
<body>

<h1>親ウィンドウ</h1>
<button id="openDialogBtn">モーダルを開く</button>
<p id="result">選択された結果: なし</p>

<script>
    let dialogWindow = null;  // 子ウィンドウを保持する変数

    // withResolversを使ってPromiseを作成
    function openModalDialog(url) {
        const { promise, resolve, reject } = Promise.withResolvers();

        const width = 500;
        const height = 400;
        const left = (window.screen.width - width) / 2;
        const top = (window.screen.height - height) / 2;

        // ウィンドウを開く
        dialogWindow = window.open(
            url,
            'modalDialog',
            `width=${width},height=${height},left=${left},top=${top}`
        );

        // ウィンドウがブロックされている場合
        if (!dialogWindow) {
            reject(new Error('ポップアップがブロックされました'));
            return promise;
        }

        // メッセージイベントのリスナーを追加(子ウィンドウからのメッセージを受信)
        window.addEventListener('message', function(event) {
            if (event.origin !== window.location.origin) {
                return;
            }
            resolve(event.data);
        });

        // ウィンドウが閉じられたことを定期的にチェック
        let interval = setInterval(() => {
            if (dialogWindow && dialogWindow.closed) {
                clearInterval(interval);
            }
        }, 500);

        return promise;
    }

    // 親ウィンドウが遷移する際に子ウィンドウを閉じる
    window.addEventListener('beforeunload', () => {
        if (dialogWindow && !dialogWindow.closed) {
            dialogWindow.close();  // 子ウィンドウを閉じる
        }
    });

    // ボタンのクリックイベントでモーダルを開く
    document.getElementById('openDialogBtn').addEventListener('click', () => {
        openModalDialog('child.html')
            .then((result) => {
                document.getElementById('result').innerText = `選択された結果: ${result}`;
            })
            .catch((error) => {
                alert(error.message);
            });
    });
</script>

</body>
</html>

修正点:

  • let dialogWindow = null; として子ウィンドウの参照をグローバル変数に保存しています。
  • window.addEventListener('beforeunload', () => { ... }); 内で、親ウィンドウがページをリロードしたり遷移する際に、子ウィンドウが開いていれば dialogWindow.close() を呼び出して子ウィンドウを閉じるようにしています。

これにより、親ウィンドウがページ遷移またはリロードされるタイミングで子ウィンドウが自動的に閉じられるようになります。

親画面を閉じさせない例

ブラウザのタブにある閉じるボタン(×)をクリックできないようにすることは、一般的には直接的にはできません。ブラウザは、Webページが自分自身やブラウザのUIを変更することを防ぐための厳格なセキュリティ対策を講じています。

ただし、Webアプリケーションを開発している場合、ユーザーがウィンドウやタブを閉じるのを防ぐために、次のようなアプローチを試みることができます。

  1. beforeunload イベントを使用する: ユーザーがページを離れようとしたときに確認ダイアログを表示することができます。以下はその例です。
   window.addEventListener('beforeunload', function (e) {
       e.preventDefault();
       e.returnValue = '';
   });
// ウィンドウを閉じるときに発動する
window.addEventListener('beforeunload', function(event) {
    event.returnValue = 'このページを離れようとしています。よろしいですか?';
});

このコードを使うと、ユーザーがナビゲートしたりタブを閉じたりしようとしたときに、メッセージが表示されます。ただし、表示されるテキストはブラウザによって決まります。

  1. フルスクリーンモード: より没入感のある体験が必要な場合は、Fullscreen APIを使用することを検討してください。ただし、これも閉じるボタンを無効にすることはできず、ユーザーはタブを閉じることができます。

  2. ユーザーインターフェースデザイン: 閉じるボタンを無効にするのではなく、ユーザーに留まるよう促すアプリケーションのフローを再設計することを検討してください。たとえば、未保存の作業についての保存プロンプトや確認を追加することができます。

具体的な要件や制約があれば教えてください。それに応じた提案をさせていただきます!

他確認事例

ブラウザのタブにある閉じるボタン(×)を無効にできない理由についての情報は、いくつかのリソースから確認できます。

  1. ブラウザのセキュリティポリシー: 多くのブラウザ(ChromeFirefoxなど)は、WebページがブラウザのUIを変更することを防ぐために、厳格なセキュリティポリシーを設けています。このため、ユーザーがブラウザを操作する際の基本的な機能(タブを閉じるなど)を無効にすることはできません

www.gtricks.com

support.mozilla.org

  1. ユーザー体験: ユーザーは自分の意志でタブを操作することが期待されており、これを無効にすることはユーザー体験を損なう可能性があります。ブラウザは、ユーザーが意図しない操作を防ぐための警告を表示することができますが、実際の閉じるボタン自体を無効にすることはできません。

gadgetstouse.com

これらの情報をもとに、ブラウザの動作についての理解を深めることができます。もしさらに詳しい内容を確認したい場合は、以下のリンクを参考にしてください:

これらのリソースでは、ブラウザの設定やタブ管理についての具体的な方法が紹介されています。