モーダル管理を“プロ品質”へ近づける 5日目のテーマ
5日目は、1〜4日目で作ってきた Modal クラス と ModalManager をさらに実践的に強化し、
「アプリ全体で安心して使えるモーダル管理」を完成形に近づける回です。
今日の学習ポイントは次の3つです。
- クラス設計を“拡張しやすい構造”に進化させる
- UI 状態制御を「外部から操作しやすい API」として整理する
- イベント伝播を応用し、複数モーダル・複雑な UI でも誤作動しない仕組みを作る
実装としては、
開く / 閉じる、背景クリック、ESCキー対応を
外部コードから安全に呼び出せる形 に整えます。
モーダルを「外部から操作しやすい API」にする
4日目までの課題
Modal クラスは内部的には良い設計になってきましたが、
外部から使うときに次のような問題が残ります。
- modal.open() を呼ぶだけでは「開く前の準備」ができない
- modal.close() を呼ぶだけでは「閉じた後の処理」ができない
- モーダルごとに違う動きをしたいときにコードが増える
そこで、5日目では
「外部から自由に振る舞いを差し込める API」
を作ります。
コールバックを「イベント風」に整理する
4日目までのコールバック
new Modal(root, {
onOpen: () => {},
onClose: () => {}
});
JavaScriptこれは悪くありませんが、
実務ではもっと柔軟な仕組みが必要になります。
5日目の改善:イベント風 API
modal.on("open", () => {
console.log("モーダルが開いた");
});
modal.on("close", () => {
console.log("モーダルが閉じた");
});
JavaScript実装例
class Modal {
constructor(root) {
this.root = root;
this.events = {};
}
on(eventName, handler) {
if (!this.events[eventName]) {
this.events[eventName] = [];
}
this.events[eventName].push(handler);
}
emit(eventName) {
const handlers = this.events[eventName] || [];
handlers.forEach(fn => fn());
}
open() {
// 開く処理…
this.emit("open");
}
close() {
// 閉じる処理…
this.emit("close");
}
}
JavaScript深掘りポイント:イベント API は“拡張性の核”
この仕組みがあると、
- モーダルごとに違う動きを簡単に追加できる
- クラス本体を改造せずに機能追加できる
- 外部コードがモーダルの状態変化に反応できる
という、実務で非常に重要なメリットが生まれます。
UI 状態制御を「外部から見える状態」として整理する
4日目までの状態
this.state = "opened";
JavaScriptこれは内部的には良いですが、
外部から状態を知りたいこともあります。
例:
「すでに開いているなら open() を呼ばない」
「閉じているときだけ close() を呼ぶ」
5日目の改善:getter を用意する
get isOpened() {
return this.state === "opened";
}
JavaScript外部からの利用例
if (!modal.isOpened) {
modal.open();
}
JavaScript深掘りポイント:状態を“読み取り専用”にするのが安全
外部からmodal.state = "closing"
のように書けてしまうと、
UI が壊れる原因になります。
getter を使うことで、
状態は読めるが書けない
という安全な設計になります。
背景クリック制御を「複雑な UI に耐える形」にする
4日目の contains() 判定
if (!this.content.contains(event.target)) {
this.close();
}
JavaScriptこれは非常に強力ですが、
5日目ではさらに一歩進めて
「クリックを許可する領域」を複数持てるようにします。
クリック許可領域を登録できるようにする
this.safeAreas = [this.content];
JavaScript外部から追加できるようにする
modal.addSafeArea(someElement);
JavaScript実装例
addSafeArea(el) {
this.safeAreas.push(el);
}
handleRootClick(event) {
const isSafe = this.safeAreas.some(el => el.contains(event.target));
if (!isSafe) {
this.close();
}
}
JavaScript深掘りポイント:safeAreas は“UI の自由度”を高める
モーダル内に
- タブ
- ドロップダウン
- スクロール領域
- 動的に追加される要素
などがある場合、
「ここは背景扱いにしたくない」という領域が増えます。
safeAreas を導入すると、
モーダルの構造が複雑になっても誤作動しない
という強力な仕組みになります。
ESC キー対応を「優先順位付き」にする
4日目までの ESC 対応
ModalManager がスタックの一番上を閉じる形でした。
5日目の改善:ESC を無効化できるようにする
例:
「削除確認モーダルは ESC で閉じられないようにしたい」
実装例
constructor(root, options = {}) {
this.disableEsc = options.disableEsc || false;
}
handleKeydown(event) {
if (event.key === "Escape" && !this.disableEsc) {
this.close();
}
}
JavaScript深掘りポイント:UI の“意図”をコードで表現する
- 間違って閉じてほしくないモーダル
- ユーザーに必ず選択させたいモーダル
- 重要な確認ダイアログ
こういったケースでは ESC を無効化する必要があります。
disableEsc は
UI の意図をコードで表現するための重要なフラグ
になります。
5日目のまとめ
今日あなたがやったことを整理するとこうなります。
- モーダルにイベント API(on / emit)を導入して拡張性を高めた
- 状態を getter で公開し、安全に外部から参照できるようにした
- safeAreas を導入して、複雑な UI でも背景クリック誤作動を防いだ
- ESC キー対応を「無効化できる」形にして UI の意図を表現した
どれも、実務でモーダルを扱うときに必ず必要になるテクニックです。

