dialogWindow && !dialogWindow.closed
を使うことで、子ウィンドウが開いているかどうかを確認できるため、isDialogOpen
フラグは実際には不要になります。この場合、dialogWindow
の状態を直接チェックすることで、子ウィンドウの管理ができます。
修正例
以下のように、isDialogOpen
フラグを削除して、dialogWindow
の状態だけで処理を行うようにコードを整理できます。
let dialogWindow = null; // グローバルスコープに移動 function openModalDialog(url) { // 既にウィンドウが開いている場合はフォーカスを戻す if (dialogWindow && !dialogWindow.closed) { dialogWindow.focus(); return; // 新たに開かない } 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) { alert('ポップアップがブロックされました'); return; } // 親ウィンドウをクリックできないようにオーバーレイを表示 document.getElementById('overlay').style.display = 'block'; document.getElementById('overlay').setAttribute('aria-hidden', 'false'); const messageHandler = (event) => { if (event.origin !== window.location.origin) { return; } // 結果を処理 document.getElementById('result').innerText = `選択された結果: ${event.data}`; closeModalDialog(); // ダイアログを閉じる }; window.addEventListener('message', messageHandler); // ウィンドウが閉じられたことを定期的にチェック let interval = setInterval(() => { if (dialogWindow && dialogWindow.closed) { clearInterval(interval); closeModalDialog(); // ダイアログを閉じる処理を呼び出す } }, 500); } // ダイアログを閉じる処理 function closeModalDialog() { document.getElementById('overlay').style.display = 'none'; document.getElementById('overlay').setAttribute('aria-hidden', 'true'); dialogWindow = null; // ダイアログの参照をクリア } // 親ウィンドウでのイベントリスナー const focusDialog = () => { if (dialogWindow && !dialogWindow.closed) { dialogWindow.focus(); } }; window.addEventListener('click', focusDialog); window.addEventListener('focus', focusDialog); window.addEventListener('resize', focusDialog); // ボタンのクリックイベントでモーダルを開く document.getElementById('openDialogBtn').addEventListener('click', () => { openModalDialog('child.html'); });
主な変更点
isDialogOpen
フラグを削除:dialogWindow && !dialogWindow.closed
のチェックだけで、子ウィンドウの状態を判断しています。- コードの簡素化: 状態管理が簡素化され、可読性が向上します。
この変更により、コードがすっきりし、メンテナンスもしやすくなります。何か他に質問や相談があれば、遠慮なくお知らせください!
別解
理解しました。親ウィンドウでの操作があるたびに子ウィンドウにフォーカスを戻したい場合、focusDialog
関数を親ウィンドウでのクリック、フォーカス、リサイズのイベントごとに実行する必要があります。
以下のように、initialize
関数でイベントリスナーを設定し、親ウィンドウでの処理が発生するたびに子ウィンドウにフォーカスを戻すようにします。
修正後のコード
let dialogWindow = null; // グローバルスコープに移動 // メッセージイベントのリスナー const messageHandler = (event) => { if (event.origin !== window.location.origin) { return; } // 結果を処理 document.getElementById('result').innerText = `選択された結果: ${event.data}`; closeModalDialog(); // ダイアログを閉じる }; // フォーカス処理のリスナー const focusDialog = () => { if (dialogWindow && !dialogWindow.closed) { dialogWindow.focus(); // 子ウィンドウにフォーカスを戻す } }; // 初期化関数 function initialize() { // 一度だけリスナーを追加 window.addEventListener('click', focusDialog); window.addEventListener('focus', focusDialog); window.addEventListener('resize', focusDialog); } function openModalDialog(url) { // 既にウィンドウが開いている場合はフォーカスを戻す if (dialogWindow && !dialogWindow.closed) { dialogWindow.focus(); return; // 新たに開かない } 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) { alert('ポップアップがブロックされました'); return; } // 親ウィンドウをクリックできないようにオーバーレイを表示 document.getElementById('overlay').style.display = 'block'; document.getElementById('overlay').setAttribute('aria-hidden', 'false'); // メッセージイベントのリスナーを追加 window.addEventListener('message', messageHandler); // ウィンドウが閉じられたことを定期的にチェック let interval = setInterval(() => { if (dialogWindow && dialogWindow.closed) { clearInterval(interval); closeModalDialog(); // ダイアログを閉じる処理を呼び出す window.removeEventListener('message', messageHandler); // メッセージリスナーを削除 } }, 500); } // ダイアログを閉じる処理 function closeModalDialog() { document.getElementById('overlay').style.display = 'none'; document.getElementById('overlay').setAttribute('aria-hidden', 'true'); dialogWindow = null; // ダイアログの参照をクリア } // 初期化関数を呼び出す initialize(); // ボタンのクリックイベントでモーダルを開く document.getElementById('openDialogBtn').addEventListener('click', () => { openModalDialog('child.html'); });
説明
- イベントリスナーの追加:
initialize
関数で、親ウィンドウのクリック、フォーカス、リサイズイベントごとにfocusDialog
を呼び出します。 - 子ウィンドウにフォーカスを戻す: これにより、親ウィンドウでのアクションが発生するたびに、子ウィンドウにフォーカスが戻ります。
この構造により、親ウィンドウでの操作に応じて、子ウィンドウが常にフォーカスを持つようになります。他に質問や改善点があれば、遠慮なくお知らせください!
initialize
関数は、親ウィンドウのクリック、フォーカス、リサイズのイベントリスナーを一度だけ追加します。しかし、その後に追加されたイベントリスナー(focusDialog
)が親ウィンドウでのアクションのたびに動作します。
イベントリスナーの仕組み
イベントリスナーの追加:
initialize
関数内で、window.addEventListener('click', focusDialog);
のようにイベントリスナーを追加します。これは一度だけ行われ、イベントリスナーは親ウィンドウの全体に対して機能します。
イベントのトリガー:
- 追加されたイベントリスナー(
focusDialog
)は、親ウィンドウでのクリック、フォーカス、リサイズイベントが発生するたびに呼び出されます。このイベントリスナーは、親ウィンドウでのアクションがあるたびに実行されるため、子ウィンドウにフォーカスを戻す処理が行われます。
- 追加されたイベントリスナー(
例
以下の流れで処理が進みます:
- ユーザーが親ウィンドウをクリックする。
- クリックイベントがトリガーされ、
focusDialog
関数が実行される。 focusDialog
内でdialogWindow.focus()
が呼び出され、子ウィンドウにフォーカスが戻る。
このように、initialize
関数が1回だけ呼び出されることで、イベントリスナーが親ウィンドウに対して設定され、その後は親ウィンドウでの各アクションに応じて focusDialog
が実行されます。
まとめ
initialize
関数はイベントリスナーを一度だけ追加しますが、そのリスナーは親ウィンドウでのアクションがあるたびに実行されるため、子ウィンドウにフォーカスを戻すことができるというわけです。この仕組みを理解していただければと思います。他に質問があれば、どうぞお知らせください!
メモリ使用量確認
イベントリスナーが何度も追加されているかどうかを確認し、メモリ使用量を監視するためのいくつかの方法があります。
1. コンソールでの確認
ブラウザの開発者ツールを使用して、特定のイベントリスナーが何回追加されているかを確認できます。
- Chrome や Firefox の開発者ツールを開きます。
- Elements タブで対象の要素を選択し、右クリックして Break on... から subtree modifications を選択します。これにより、DOMの変更時にブレークポイントが発動します。
- その後、親ウィンドウでクリックやフォーカスなどのイベントを発生させ、リスナーが何度追加されているかを確認します。
2. リスナーのカウント
イベントリスナーを追加する際にカウンタを用意し、リスナーが追加されるたびにそのカウントを増やす方法があります。
let eventListenerCount = 0; function initialize() { // クリックイベントのリスナーを追加 window.addEventListener('click', focusDialog); eventListenerCount++; console.log(`Added click listener. Total listeners: ${eventListenerCount}`); // フォーカスイベントのリスナーを追加 window.addEventListener('focus', focusDialog); eventListenerCount++; console.log(`Added focus listener. Total listeners: ${eventListenerCount}`); // リサイズイベントのリスナーを追加 window.addEventListener('resize', focusDialog); eventListenerCount++; console.log(`Added resize listener. Total listeners: ${eventListenerCount}`); }
3. メモリの使用量を監視
ブラウザの開発者ツールでメモリ使用量を監視することができます。特に Memory タブを使用して、以下の方法でメモリ使用量をチェックします。
- Heap Snapshot: 現在のメモリ使用状況のスナップショットを取得します。このスナップショットを比較することで、メモリリークの有無を確認できます。
- Allocation Timeline: アロケーションのタイムラインを監視して、特定のアクションがメモリ使用量にどのように影響するかを確認できます。
4. パフォーマンスプロファイリング
- Performance タブでプロファイリングを行い、イベントリスナーの追加や削除がどのようにパフォーマンスに影響を与えているかを確認できます。記録を開始し、親ウィンドウの操作を行い、イベントリスナーが実行されるたびにどのようなパフォーマンスの変化があるかを観察します。
まとめ
これらの方法を使って、イベントリスナーの追加回数やメモリ使用量を確認することができます。特に、リスナーが重複して追加されることによるパフォーマンスへの影響を防ぐために、必要なリスナーを適切に管理することが重要です。何か他に質問や具体的な問題があれば、お気軽にお知らせください!