モーダル管理を“アプリ品質”へ引き上げる 4日目のテーマ
4日目は、1〜3日目で作ってきた Modal クラス と ModalManager をさらに強化し、
「実務レベルで壊れないモーダル」を作るための仕上げに入ります。
今日の学習ポイントは次の3つです。
- クラス設計を“責務ごとに分割”して読みやすくする
- UI 状態制御を「アニメーション」と結びつけて正確にする
- イベント伝播を応用し、クリック・キー操作の誤作動を完全に防ぐ
そして実装としては、
開く / 閉じる、背景クリック、ESCキー対応を
アニメーション込みで正しく動く形 に仕上げます。
モーダルの「アニメーション」と状態遷移を同期させる
アニメーションがあると状態管理が難しくなる理由
モーダルは多くの場合、
フェードイン・フェードアウトなどのアニメーションを持ちます。
しかし、アニメーションがあると次の問題が起きます。
- 開き途中に閉じると、見た目と状態がズレる
- 閉じ途中に再度 open() を呼ぶと壊れる
- アニメーション終了前にイベントが発火してしまう
これを防ぐには、
状態遷移とアニメーションを同期させる 必要があります。
状態を4段階にする
this.state = "closed";
// "opening" | "opened" | "closing" | "closed"
JavaScriptopen() をアニメーション対応にする
open() {
if (this.state !== "closed") return;
this.state = "opening";
this.root.classList.add("is-open");
this.root.addEventListener("transitionend", () => {
if (this.state === "opening") {
this.state = "opened";
}
}, { once: true });
}
JavaScript深掘りポイント:transitionend を使う理由
CSS アニメーションは 時間がズレる ことがあります。
- PC の負荷
- ブラウザの最適化
- CSS の変更
setTimeout ではズレを吸収できません。
transitionend は
「アニメーションが本当に終わった瞬間」 を教えてくれるため、
状態遷移と UI を完全に同期できます。
閉じる処理もアニメーションと同期させる
close() の実装
close() {
if (this.state !== "opened") return;
this.state = "closing";
this.root.classList.remove("is-open");
this.root.addEventListener("transitionend", () => {
if (this.state === "closing") {
this.state = "closed";
}
}, { once: true });
}
JavaScript深掘りポイント:状態遷移があると「二重閉じ」を防げる
状態が "opened" のときだけ close() を許可することで、
- 開き途中に閉じる
- 閉じ途中に再度閉じる
- 開いていないのに閉じる
といった UI の破綻を防げます。
背景クリック制御を“構造変化に強い”形にする
3日目までの方法
- backdrop に click を付ける
- content に stopPropagation を付ける
これは基本として正しいですが、
モーダルの構造が変わると壊れやすいという弱点があります。
4日目の方法:クリック位置で判定する
this.root.addEventListener("click", (event) => {
if (!this.content.contains(event.target)) {
this.close();
}
});
JavaScript仕組み
- root(モーダル全体)でクリックを受ける
- content の内側かどうかを contains() で判定
- 内側なら無視、外側なら閉じる
深掘りポイント:contains() は“構造変化に強い”
モーダル内に要素が増えても、
ネストが深くなっても、
content.contains(event.target) は正しく判定できます。
stopPropagation に頼らないため、
UI の構造が変わっても壊れない のが最大のメリットです。
ESC キー対応を“アニメーション中は無効”にする
問題点
アニメーション中に ESC を押すと、
- 開き途中で閉じる
- 閉じ途中でさらに閉じる
- 状態がズレる
という事故が起きます。
解決策:opened のときだけ ESC を受け付ける
handleKeydown(event) {
if (event.key === "Escape" && this.state === "opened") {
this.close();
}
}
JavaScript深掘りポイント:状態遷移が UI の安全装置になる
状態が "opened" のときだけ閉じるようにすることで、
アニメーション中の誤作動を完全に防げます。
クラス設計を“責務ごとに分割”して読みやすくする
Modal クラスの責務
- UI の状態管理
- 開く / 閉じる
- イベント登録
- アニメーション同期
ModalManager の責務
- 複数モーダルの管理
- ESC キーの一元管理
- モーダルのスタック管理
深掘りポイント:責務を分けるとコードが壊れにくい
UI コンポーネントは、
1つのクラスが多くの責務を持つと壊れやすくなる
という性質があります。
Modal と ModalManager を分けることで、
- どこを直せばいいか分かりやすい
- 機能追加がしやすい
- バグが起きにくい
というメリットが生まれます。
4日目のまとめ
今日あなたがやったことを整理するとこうなります。
- モーダルの状態遷移をアニメーションと同期させた
- transitionend を使って UI のズレを防いだ
- 背景クリックを contains() 判定で安全にした
- ESC キー対応を「opened のときだけ」に制限した
- Modal と ModalManager の責務を明確に分けた
どれも、実務でモーダルを扱うときに必ず必要になるテクニックです。

