モーダル管理を“より実務的に強くする”3日目のテーマ
3日目は、1〜2日目で作った Modal クラス と ModalManager をさらに発展させ、
「アプリで本当に使えるモーダル管理」を完成形に近づける回です。
今日の学習ポイントは次の3つです。
- クラス設計を“拡張しやすい形”に進化させる
- UI 状態制御を「状態遷移」として扱う
- イベント伝播を応用し、複雑なクリック制御を安全にする
そして実装としては、
開く / 閉じる、背景クリック、ESCキー対応を
より堅牢で、複数モーダルでも破綻しない形 に仕上げます。
モーダルの「状態遷移」を導入して UI を安定させる
状態を boolean だけで持つ限界
2日目までは isOpen を true/false で管理していました。
しかし、実際の UI はもっと複雑です。
- 開き始めた(アニメーション中)
- 開き終わった
- 閉じ始めた
- 閉じ終わった
このように、UI には「途中の状態」が存在します。
状態を文字列で管理する
this.state = "closed";
// "opening" | "opened" | "closing" | "closed"
JavaScript状態遷移の例
open() {
if (this.state !== "closed") return;
this.state = "opening";
this.root.classList.add("is-open");
setTimeout(() => {
this.state = "opened";
}, 200); // アニメーション時間
}
JavaScript深掘りポイント:状態遷移は UI の“真実の源泉”
状態を文字列で管理すると、
- 「開いている途中に閉じる」などの事故を防げる
- アニメーションと状態がズレない
- 複数モーダルでも状態が衝突しない
UI の安定性が一気に上がります。
背景クリック制御を“誤作動ゼロ”にする応用テクニック
2日目までの背景クリック
背景要素 .modal__backdrop にクリックイベントを付ける方法でした。
これは基本として正しいですが、
実務では次のようなケースが出てきます。
- モーダルの中にスクロール領域がある
- モーダル内でドラッグ操作がある
- モーダル内でクリックイベントを多用する
こうなると、
「背景クリックと中身クリックの判定」が難しくなります。
より安全な判定方法:クリック位置で判断する
handleRootClick(event) {
if (!this.content.contains(event.target)) {
this.close();
}
}
JavaScript仕組み
- モーダル全体(root)にクリックイベントを付ける
- クリックされた要素が content の内側かどうかを判定
- 内側なら無視、外側なら閉じる
深掘りポイント:contains() は“クリック判定の最強ツール”
element.contains(target) は
「target が element の内側にあるか?」を判定します。
これにより、
- stopPropagation を使わなくてもよい
- モーダル構造が変わっても壊れない
- ネストした要素でも正しく判定できる
というメリットがあります。
ESC キー対応を“複数モーダルの優先順位”まで考える
複数モーダルが重なるケース
実務では、
「モーダルの上に別のモーダルが開く」
ということが普通にあります。
例:
設定モーダル → 中の「削除確認」モーダル
このとき、ESC を押したら
一番上のモーダルだけ閉じる
必要があります。
ModalManager にスタック(stack)を導入する
class ModalManager {
constructor() {
this.stack = [];
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
const top = this.stack[this.stack.length - 1];
if (top) top.close();
}
});
}
push(modal) {
this.stack.push(modal);
}
pop(modal) {
this.stack = this.stack.filter(m => m !== modal);
}
}
JavaScriptモーダル側で登録する
open() {
if (this.state !== "closed") return;
this.manager.push(this);
this.state = "opened";
}
close() {
this.manager.pop(this);
this.state = "closed";
}
JavaScript深掘りポイント:スタック構造は“UI の重なり”を自然に表現する
スタック(LIFO)は
「最後に開いたものが最初に閉じる」
というモーダルの性質と完全に一致します。
クラス設計を“拡張しやすい形”にする
コールバックを導入する
モーダルが開いたとき、閉じたときに
外部で処理をしたいことがあります。
例:
- 開いたらフォームを初期化したい
- 閉じたらスクロール位置を戻したい
コールバックを受け取れるようにする
class Modal {
constructor(root, options = {}) {
this.root = root;
this.onOpen = options.onOpen || (() => {});
this.onClose = options.onClose || (() => {});
}
open() {
this.state = "opened";
this.onOpen();
}
close() {
this.state = "closed";
this.onClose();
}
}
JavaScript深掘りポイント:クラスは“拡張ポイント”を持つと強くなる
コールバックを用意しておくと、
- クラスを改造しなくても機能追加できる
- 外部から自由に振る舞いを変えられる
- 再利用性が高くなる
というメリットがあります。
3日目のまとめ
今日あなたがやったことを整理するとこうなります。
- モーダルの状態を「状態遷移」として扱い、UI を安定させた
- 背景クリックを contains() 判定でより安全にした
- ModalManager にスタックを導入し、複数モーダルの優先順位を管理した
- コールバックを導入して、クラスを拡張しやすくした
どれも、実務でモーダルを扱うときに必ず必要になるテクニックです。

