JavaScript | 1 日 120 分 × 7 日アプリ学習:モーダルウィンドウ管理

Web APP JavaScript
スポンサーリンク

モーダル管理を“アプリ品質”へ引き上げる 4日目のテーマ

4日目は、1〜3日目で作ってきた Modal クラスModalManager をさらに強化し、
「実務レベルで壊れないモーダル」を作るための仕上げに入ります。

今日の学習ポイントは次の3つです。

  • クラス設計を“責務ごとに分割”して読みやすくする
  • UI 状態制御を「アニメーション」と結びつけて正確にする
  • イベント伝播を応用し、クリック・キー操作の誤作動を完全に防ぐ

そして実装としては、
開く / 閉じる、背景クリック、ESCキー対応を
アニメーション込みで正しく動く形 に仕上げます。


モーダルの「アニメーション」と状態遷移を同期させる

アニメーションがあると状態管理が難しくなる理由

モーダルは多くの場合、
フェードイン・フェードアウトなどのアニメーションを持ちます。

しかし、アニメーションがあると次の問題が起きます。

  • 開き途中に閉じると、見た目と状態がズレる
  • 閉じ途中に再度 open() を呼ぶと壊れる
  • アニメーション終了前にイベントが発火してしまう

これを防ぐには、
状態遷移とアニメーションを同期させる 必要があります。

状態を4段階にする

this.state = "closed";
// "opening" | "opened" | "closing" | "closed"
JavaScript

open() をアニメーション対応にする

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 の責務を明確に分けた

どれも、実務でモーダルを扱うときに必ず必要になるテクニックです。

タイトルとURLをコピーしました