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

Web APP JavaScript
スポンサーリンク

1日目のゴールと今日やること

1日目のテーマは
「モーダルウィンドウを“クラス”として設計し、きれいに開閉を制御できるようになる」
ことです。

今日のゴールは、ざっくり言うとこの3つです。

  • モーダルを「1つのクラス」として設計してみる
  • UI の状態(開いている / 閉じている)をコードで管理する感覚をつかむ
  • 背景クリックと ESC キーで閉じる処理を、イベント伝播を意識しながら書く

「なんとなく動けばOK」ではなく、
“あとから読んでも分かりやすいモーダル” を目指します。


モーダルウィンドウとは何かを整理する

モーダルの役割

モーダルは、画面の上に「手前のレイヤー」として出てくるウィンドウです。

  • 背景は暗くなる
  • ユーザーに「今はこのモーダルに集中してね」と伝える
  • OK / キャンセルなどの操作をさせる

よくある例は、
「本当に削除しますか?」の確認ダイアログです。

今日作るモーダルの仕様

1日目では、シンプルにこうします。

  • ボタンを押すとモーダルが開く
  • モーダル内の「閉じる」ボタンで閉じる
  • 背景(黒い部分)をクリックすると閉じる
  • ESC キーでも閉じる

これを クラスで管理する のが今日のメインテーマです。


HTML と CSS の前提イメージ

HTML の構造イメージ

<button id="openModal">モーダルを開く</button>

<div class="modal" id="myModal">
  <div class="modal__backdrop"></div>
  <div class="modal__content">
    <p>モーダルの中身です</p>
    <button class="modal__close">閉じる</button>
  </div>
</div>

ポイントは「モーダル全体」と「背景」と「中身」が分かれていることです。

  • .modal … 全体(表示 / 非表示を切り替える)
  • .modal__backdrop … 黒い背景
  • .modal__content … 白いウィンドウ本体

CSS はここでは細かくやりませんが、
.modaldisplay: none; を付けておいて、
「開くときにクラスを付けて表示する」という形を想定します。


クラスでモーダルを管理するという発想

なぜクラスにするのか

モーダルを「バラバラの関数」で管理すると、こうなりがちです。

function openModal() { ... }
function closeModal() { ... }
function onBackdropClick() { ... }
function onKeydown() { ... }
JavaScript

これでも動きますが、

  • どの関数がどのモーダルに関係しているのか分かりにくい
  • モーダルが2つになった瞬間に地獄になる

そこで、「モーダル1つ = クラス1つ」 という形にします。

クラスのイメージ

class Modal {
  constructor(rootElement) {
    this.root = rootElement;
  }

  open() {}
  close() {}
  bindEvents() {}
}
JavaScript

「モーダルに関することは、このクラスの中に閉じ込める」
という考え方です。


モーダルクラスの基本設計

コンストラクタで「要素」を握る

class Modal {
  constructor(root) {
    this.root = root;
    this.backdrop = root.querySelector(".modal__backdrop");
    this.content = root.querySelector(".modal__content");
    this.closeButton = root.querySelector(".modal__close");

    this.isOpen = false;

    this.handleBackdropClick = this.handleBackdropClick.bind(this);
    this.handleKeydown = this.handleKeydown.bind(this);

    this.bindEvents();
  }
}
JavaScript

ここでやっていることを分解すると:

  • root … モーダル全体の要素(#myModal
  • backdrop … 背景の要素
  • content … 中身の要素
  • closeButton … 閉じるボタン
  • isOpen … 「今開いているかどうか」を持つフラグ
  • bind しているのは、イベントで this を正しく保つため

深掘りポイント:UI 状態を「isOpen」で持つ意味

isOpen は、
「今このモーダルは開いているのか?」
を表すフラグです。

これを持っておくと、

  • すでに開いているのにまた open しようとしたときに無視できる
  • ESC キーで閉じるとき、「開いているときだけ反応する」ようにできる

など、状態に応じた制御 がしやすくなります。


開く / 閉じるの実装

open メソッド

open() {
  if (this.isOpen) return;

  this.root.classList.add("is-open");
  this.isOpen = true;

  document.addEventListener("keydown", this.handleKeydown);
}
JavaScript

close メソッド

close() {
  if (!this.isOpen) return;

  this.root.classList.remove("is-open");
  this.isOpen = false;

  document.removeEventListener("keydown", this.handleKeydown);
}
JavaScript

深掘りポイント:UI と状態とイベントをセットで考える

open でやっていることは 3 つです。

  • 見た目を変える(クラスを付ける)
  • 状態を変える(isOpen = true)
  • キーボードイベントを登録する

close ではその逆をやっています。

「見た目」「状態」「イベント」
この3つは、UI を設計するときにいつもセットで考えます。


背景クリックで閉じる処理とイベント伝播

背景クリックのイベント登録

bindEvents() {
  if (this.backdrop) {
    this.backdrop.addEventListener("click", this.handleBackdropClick);
  }

  if (this.closeButton) {
    this.closeButton.addEventListener("click", () => this.close());
  }
}
JavaScript

背景クリックのハンドラ

handleBackdropClick(event) {
  this.close();
}
JavaScript

これだけでも「背景をクリックしたら閉じる」は実現できます。

ここで出てくる問題:中身をクリックしても閉じてしまう?

今回の構造では、
.modal__backdrop.modal__content は別要素なので、
背景だけにイベントを付けていれば問題は起きません。

ただし、よくある別パターンとして
「モーダル全体にクリックイベントを付けて、
中身のクリックは無視したい」というケースがあります。

そのときに重要になるのが イベント伝播 です。


イベント伝播を意識したクリック制御の例

モーダル全体にクリックイベントを付ける場合

<div class="modal" id="myModal">
  <div class="modal__content">
    ...
  </div>
</div>
JavaScript

この構造で「背景クリックで閉じる」をやろうとして、
.modal にクリックイベントを付けるとします。

this.root.addEventListener("click", () => {
  this.close();
});
JavaScript

すると、
.modal__content をクリックしてもイベントが親に伝播して、
モーダルが閉じてしまいます。

そこで stopPropagation

this.content.addEventListener("click", (event) => {
  event.stopPropagation();
});
JavaScript

こうすると、
.modal__content 内で発生したクリックイベントは
.modal まで届かなくなります。

深掘りポイント:イベント伝播は「内側 → 外側」に流れる

クリックイベントは、

一番内側の要素
→ 親
→ さらに親
→ …
→ document

という順番で伝わっていきます。

event.stopPropagation()
「これ以上外側に伝えないで」
という指示です。

モーダルのように
「中身のクリックは無視したいけど、背景のクリックは拾いたい」
という UI では、イベント伝播の理解がとても重要になります。


ESC キーで閉じる処理

キーボードイベントのハンドラ

handleKeydown(event) {
  if (event.key === "Escape") {
    this.close();
  }
}
JavaScript

open / close との連携

すでに書いたように、
open で document.addEventListener("keydown", this.handleKeydown);
close で removeEventListener をしています。

深掘りポイント:必要なときだけイベントを登録する

常に keydown を監視していると、

  • モーダルが閉じているのに ESC で何かが動いてしまう
  • ページ全体のキーボード操作に悪影響が出る

といった問題が起きます。

「モーダルが開いているときだけ ESC を受け付ける」
という設計にすることで、
UI の一貫性と安全性が高まります。


実際にインスタンスを作って動かす

初期化コード

const modalElement = document.getElementById("myModal");
const openButton = document.getElementById("openModal");

const modal = new Modal(modalElement);

openButton.addEventListener("click", () => {
  modal.open();
});
JavaScript

これで、

  • ボタンを押すとモーダルが開く
  • 閉じるボタンで閉じる
  • 背景クリックで閉じる
  • ESC キーで閉じる

という一連の流れが、
Modal クラスの中にきれいにまとまった状態 になります。


1日目のまとめ

今日あなたがやったことを整理すると、こうなります。

  • モーダルを「クラス」として設計する発想を持った
  • UI 状態(isOpen)をフラグで管理する感覚をつかんだ
  • 開く / 閉じるで「見た目」「状態」「イベント」をセットで制御した
  • 背景クリックとイベント伝播の関係を理解した
  • ESC キー対応を「開いているときだけ有効」にする設計を体験した

どれも、実務でモーダルを実装するときに
必ず意識するポイントです。


今日いちばん深く理解してほしいこと

1日目の本質は、

「UI コンポーネント(モーダル)を“クラス + 状態 + イベント”で設計する」

という感覚です。

  • モーダルに関することは Modal クラスに閉じ込める
  • isOpen で状態を持つ
  • open / close で UI とイベントを切り替える
  • イベント伝播を理解して、クリックの挙動をコントロールする

ここまでイメージできていたら、
あなたはもう「とりあえず動けばいい」から
“設計して作る側” に一歩踏み出しています。

2日目では、この Modal クラスを
「複数モーダル対応」「コールバック対応」などに発展させていきます。

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