JavaScript Tips | UI・画面部品:Modalを閉じる

JavaScript JavaScript
スポンサーリンク
スポンサーリンク
  1. Modalを閉じるユーティリティは「ユーザーを元の画面にスムーズに“帰す”ための仕組み」です
  2. Modalの「閉じ方」を整理する(どこから閉じられるべきか)
    1. よくある閉じ方のパターン
    2. 業務システムで意識したいこと
  3. ステップ1:CSS側で「閉じている状態」を定義しておく
    1. 例:CSSの基本(復習)
  4. ステップ2:Modalを閉じる基本ユーティリティ
    1. 基本ユーティリティ
    2. 使用例
  5. ステップ3:×ボタン・キャンセルボタンからModalを閉じる
    1. HTML例
    2. JavaScriptで閉じるイベントを設定する
  6. ステップ4:OKボタンで処理を実行したあとにModalを閉じる
    1. HTML例(確認Modal)
    2. JavaScriptで「処理+閉じる」を紐づける
  7. ステップ5:EscキーでModalを閉じる(キーボード操作への配慮)
    1. Escキーで閉じる処理の例
    2. 複数Modalがある場合の考え方
  8. ステップ6:背景クリックでModalを閉じるかどうかを設計する
    1. 背景クリックで閉じる実装例
    2. 業務システムでの注意点
  9. ステップ7:閉じた後のフォーカスをどうするか(重要な深掘り)
    1. 理想的な動き
    2. 実装の考え方
    3. 使用例(開く側でトリガー要素を渡す)
  10. ステップ8:業務で使える「Modalを閉じるユーティリティ」まとめ
    1. 使用例(全体の初期化)
  11. ステップ9:絶対に避けるべき「閉じ方の失敗」を明確にしておく
    1. 1. 閉じる手段がないModal
    2. 2. 閉じた後の状態が不自然
    3. 3. 閉じるタイミングが不明確
  12. まとめ:Modalを閉じるユーティリティは「ユーザーを元の画面にスムーズに帰すための、実務的で欠かせないUI技術」です

Modalを閉じるユーティリティは「ユーザーを元の画面にスムーズに“帰す”ための仕組み」です

業務システムでModal(モーダルダイアログ)を使うとき、 「開く」こと以上に大事なのが 「どう閉じるか」 です。

  • 閉じられないModalは、ユーザーを画面に閉じ込めてしまいます。
  • 閉じ方がバラバラだと、ユーザーは毎回迷います。
  • 閉じた後のフォーカスや状態が不自然だと、操作感が悪くなります。

つまり、Modalを閉じるユーティリティは、

「ユーザーを元の画面にスムーズに帰すための、UIの出口設計」

だと考えると分かりやすいです。

ここでは、初心者向けに、

  • Modalの閉じ方のパターン
  • JavaScriptでModalを閉じるユーティリティ
  • 業務で使いやすい設計と注意点

をステップバイステップで解説していきます。

Modalの「閉じ方」を整理する(どこから閉じられるべきか)

まず、Modalを閉じる手段にはどんなものがあるかを整理します。

よくある閉じ方のパターン

  • ×ボタンをクリックする
    • Modalの右上にある「閉じる」ボタン。
  • キャンセルボタンをクリックする
    • フッターにある「キャンセル」「閉じる」などのボタン。
  • OKボタンをクリックした後に閉じる
    • 処理を実行したあと、Modalを閉じる。
  • Escキーを押す
    • キーボード操作で閉じる。
  • 背景(バックドロップ)をクリックする
    • 画面の暗い部分をクリックすると閉じる(ただし業務システムでは慎重に)。

業務システムで意識したいこと

  • 「必ず閉じられる手段」が最低1つ以上あること。
  • ユーザーが迷わないように、閉じ方を統一すること。
  • 誤操作で閉じてしまうと困る場面では、背景クリックで閉じないようにすること。

ポイント:Modalは「ユーザーの集中を一時的に集める場」なので、 そこから自然に抜けられる出口をきちんと設計することが重要です。

ステップ1:CSS側で「閉じている状態」を定義しておく

Modalを閉じるユーティリティを作る前に、 CSS側で「閉じている状態」を定義しておく必要があります。

例:CSSの基本(復習)

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.4);
  display: none;          /* 閉じている状態 */
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.modal-backdrop.is-open {
  display: flex;          /* 開いている状態 */
}

ここでは、

  • .modal-backdrop が「閉じている状態」(非表示)
  • .modal-backdrop.is-open が「開いている状態」(表示)

というルールにしています。

JavaScript側では、この is-open クラスを外すことで 「閉じる」 を実現します。

ステップ2:Modalを閉じる基本ユーティリティ

次に、JavaScriptでModalを閉じるための基本ユーティリティを作ります。

基本ユーティリティ

export const ModalController = {
  open(modalId) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.add("is-open");
    el.setAttribute("aria-hidden", "false");
  },

  close(modalId) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.remove("is-open");
    el.setAttribute("aria-hidden", "true");
  },
};
JavaScript

使用例

// Modalを閉じる
ModalController.close("my-modal");
JavaScript

この close 関数は、

  • is-open クラスを外す
  • aria-hidden="true" を設定する

ことで、Modalを「閉じている状態」に戻します。

ポイント:閉じる処理は「クラスを外す+アクセシビリティ属性を更新する」というシンプルな形にしておくと、どのModalでも同じように扱えます。

ステップ3:×ボタン・キャンセルボタンからModalを閉じる

Modalの中には、閉じるためのボタンが複数あることが多いです。

HTML例

<button type="button" data-modal-open="my-modal">
  詳細を見る
</button>

<div class="modal-backdrop" id="my-modal" aria-hidden="true">
  <div class="modal">
    <div class="modal-header">
      <h2 class="modal-title">詳細情報</h2>
      <button class="modal-close" type="button" aria-label="閉じる">×</button>
    </div>
    <div class="modal-body">
      ここに詳細情報が入ります。
    </div>
    <div class="modal-footer">
      <button type="button" class="modal-cancel">キャンセル</button>
      <button type="button" class="modal-ok">OK</button>
    </div>
  </div>
</div>

JavaScriptで閉じるイベントを設定する

function setupModalCloseButtons() {
  document.querySelectorAll(".modal-backdrop").forEach((backdrop) => {
    const modalId = backdrop.id;

    // ×ボタン
    const closeBtn = backdrop.querySelector(".modal-close");
    if (closeBtn) {
      closeBtn.addEventListener("click", () => {
        ModalController.close(modalId);
      });
    }

    // キャンセルボタン
    const cancelBtn = backdrop.querySelector(".modal-cancel");
    if (cancelBtn) {
      cancelBtn.addEventListener("click", () => {
        ModalController.close(modalId);
      });
    }
  });
}

document.addEventListener("DOMContentLoaded", () => {
  setupModalCloseButtons();
});
JavaScript

これにより、

  • ×ボタンを押すと閉じる
  • キャンセルボタンを押すと閉じる

という基本的な閉じ方が実現できます。

ステップ4:OKボタンで処理を実行したあとにModalを閉じる

業務システムでは、「OKボタンを押したら処理を実行し、その後Modalを閉じる」という流れがよくあります。

HTML例(確認Modal)

<button type="button" data-modal-open="delete-confirm-modal">
  削除
</button>

<div class="modal-backdrop" id="delete-confirm-modal" aria-hidden="true">
  <div class="modal">
    <div class="modal-header">
      <h2 class="modal-title">削除確認</h2>
      <button class="modal-close" type="button" aria-label="閉じる">×</button>
    </div>
    <div class="modal-body">
      このレコードを削除します。よろしいですか?
    </div>
    <div class="modal-footer">
      <button type="button" class="modal-cancel">キャンセル</button>
      <button type="button" class="modal-ok">削除する</button>
    </div>
  </div>
</div>

JavaScriptで「処理+閉じる」を紐づける

function setupDeleteConfirmModal(onConfirm) {
  const backdrop = document.getElementById("delete-confirm-modal");
  if (!backdrop) return;

  const okBtn = backdrop.querySelector(".modal-ok");
  const modalId = backdrop.id;

  if (okBtn) {
    okBtn.addEventListener("click", () => {
      // 先に処理を実行
      onConfirm();
      // その後Modalを閉じる
      ModalController.close(modalId);
    });
  }

  // ×とキャンセルは前述の setupModalCloseButtons で閉じる
}

// 使用例
document.addEventListener("DOMContentLoaded", () => {
  setupDeleteConfirmModal(() => {
    console.log("削除処理を実行します");
  });
});
JavaScript

ポイント:OKボタンの動作は「処理」と「閉じる」をセットで考え、 ユーティリティとしてまとめておくと再利用性が高まります。

ステップ5:EscキーでModalを閉じる(キーボード操作への配慮)

キーボード操作を重視するユーザーにとって、 EscキーでModalを閉じられるかどうかは使いやすさに直結します。

Escキーで閉じる処理の例

function setupModalEscClose(modalId) {
  document.addEventListener("keydown", (event) => {
    if (event.key === "Escape") {
      ModalController.close(modalId);
    }
  });
}

// 使用例
document.addEventListener("DOMContentLoaded", () => {
  setupModalEscClose("my-modal");
});
JavaScript

複数Modalがある場合の考え方

  • 「今開いているModalだけ」を閉じるようにしたい場合は、 開いているModalを管理する仕組み(スタックなど)を用意する必要があります。
  • シンプルなシステムでは、「特定の重要なModalだけEscで閉じる」でも十分なことが多いです。

ポイント:Escキーで閉じる機能は便利ですが、 「誤って閉じてしまうと困る場面」では慎重に導入する必要があります。

ステップ6:背景クリックでModalを閉じるかどうかを設計する

背景(バックドロップ)をクリックしたときにModalを閉じるかどうかは、 ユーザー体験と安全性のバランスを考えるポイントです。

背景クリックで閉じる実装例

function setupBackdropClickClose() {
  document.querySelectorAll(".modal-backdrop").forEach((backdrop) => {
    const modalId = backdrop.id;

    backdrop.addEventListener("click", (event) => {
      // 背景部分をクリックしたときだけ閉じる
      if (event.target === backdrop) {
        ModalController.close(modalId);
      }
    });
  });
}

document.addEventListener("DOMContentLoaded", () => {
  setupBackdropClickClose();
});
JavaScript

業務システムでの注意点

  • 入力途中のフォームがあるModalでは、 背景クリックで閉じると「うっかり消してしまう」事故が起きやすいです。
  • 確認ダイアログなど、短時間で閉じるModalでは、 背景クリックで閉じるのも許容されることが多いです。

結論:背景クリックで閉じるかどうかは「Modalの用途ごと」に決めるのが安全です。 重要な入力を伴うModalでは、背景クリックで閉じない方がよい場合が多いです。

ステップ7:閉じた後のフォーカスをどうするか(重要な深掘り)

Modalを閉じた後、 ユーザーのフォーカス(キーボード操作の対象)がどこに戻るかは、 アクセシビリティと使いやすさに大きく影響します。

理想的な動き

  • Modalを開いたボタンにフォーカスを戻す
    • 例:「詳細を見る」ボタン → Modal → 閉じる → 「詳細を見る」ボタンに戻る

実装の考え方

Modalを開くときに「開いた元の要素」を記録しておき、 閉じるときにそこへフォーカスを戻します。

export const ModalController = {
  _lastTrigger: new Map(), // modalId -> element

  open(modalId, triggerElement = null) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.add("is-open");
    el.setAttribute("aria-hidden", "false");

    if (triggerElement) {
      this._lastTrigger.set(modalId, triggerElement);
    }

    const focusTarget =
      el.querySelector(".modal-ok") ||
      el.querySelector(".modal-close") ||
      el.querySelector("button") ||
      el;
    if (focusTarget && typeof focusTarget.focus === "function") {
      focusTarget.focus();
    }
  },

  close(modalId) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.remove("is-open");
    el.setAttribute("aria-hidden", "true");

    const trigger = this._lastTrigger.get(modalId);
    if (trigger && typeof trigger.focus === "function") {
      trigger.focus();
    }
  },
};
JavaScript

使用例(開く側でトリガー要素を渡す)

document.querySelectorAll("[data-modal-open]").forEach((btn) => {
  const modalId = btn.getAttribute("data-modal-open");
  btn.addEventListener("click", () => {
    ModalController.open(modalId, btn);
  });
});
JavaScript

ポイント:閉じた後のフォーカスを「元のボタン」に戻すことで、 ユーザーは自然に操作を続けることができ、キーボード操作でもストレスが減ります。

ステップ8:業務で使える「Modalを閉じるユーティリティ」まとめ

ここまでの内容を、ひとつのユーティリティとして整理してみます。

export const ModalController = {
  _lastTrigger: new Map(),

  open(modalId, triggerElement = null) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.add("is-open");
    el.setAttribute("aria-hidden", "false");

    if (triggerElement) {
      this._lastTrigger.set(modalId, triggerElement);
    }

    const focusTarget =
      el.querySelector(".modal-ok") ||
      el.querySelector(".modal-close") ||
      el.querySelector("button") ||
      el;
    if (focusTarget && typeof focusTarget.focus === "function") {
      focusTarget.focus();
    }
  },

  close(modalId) {
    const el = document.getElementById(modalId);
    if (!el) return;
    el.classList.remove("is-open");
    el.setAttribute("aria-hidden", "true");

    const trigger = this._lastTrigger.get(modalId);
    if (trigger && typeof trigger.focus === "function") {
      trigger.focus();
    }
  },
};

export function setupModalCloseButtons() {
  document.querySelectorAll(".modal-backdrop").forEach((backdrop) => {
    const modalId = backdrop.id;

    const closeBtn = backdrop.querySelector(".modal-close");
    if (closeBtn) {
      closeBtn.addEventListener("click", () => {
        ModalController.close(modalId);
      });
    }

    const cancelBtn = backdrop.querySelector(".modal-cancel");
    if (cancelBtn) {
      cancelBtn.addEventListener("click", () => {
        ModalController.close(modalId);
      });
    }
  });
}

export function setupBackdropClickClose() {
  document.querySelectorAll(".modal-backdrop").forEach((backdrop) => {
    const modalId = backdrop.id;

    backdrop.addEventListener("click", (event) => {
      if (event.target === backdrop) {
        ModalController.close(modalId);
      }
    });
  });
}
JavaScript

使用例(全体の初期化)

document.addEventListener("DOMContentLoaded", () => {
  // 開くトリガー
  document.querySelectorAll("[data-modal-open]").forEach((btn) => {
    const modalId = btn.getAttribute("data-modal-open");
    btn.addEventListener("click", () => {
      ModalController.open(modalId, btn);
    });
  });

  // 閉じるボタン
  setupModalCloseButtons();

  // 必要に応じて背景クリックで閉じる
  // setupBackdropClickClose();
});
JavaScript

ステップ9:絶対に避けるべき「閉じ方の失敗」を明確にしておく

1. 閉じる手段がないModal

  • ×ボタンもキャンセルボタンもない
  • Escキーでも閉じられない

これは、ユーザーを「UIの袋小路」に追い込んでしまうため、 業務システムでは絶対に避けるべきです。

2. 閉じた後の状態が不自然

  • 閉じた後に、どこにフォーカスがあるか分からない
  • 閉じた後に、背景側のボタンが押せない状態になっている

これは、ユーザーの操作感を大きく損ねます。

3. 閉じるタイミングが不明確

  • OKボタンを押しても、処理が終わるまでModalが閉じないのか、すぐ閉じるのかが分からない
  • エラーが出てもModalが閉じてしまい、何が起きたか分からない

業務システムでは、「閉じるタイミング」と「エラー表示の場所」を 明確に設計することが重要です。

まとめ:Modalを閉じるユーティリティは「ユーザーを元の画面にスムーズに帰すための、実務的で欠かせないUI技術」です

Modalを閉じるためのJavaScriptユーティリティと設計を導入することで、次のような効果があります。

  • どのModalでも同じ方法で閉じられるようになり、ユーザーが迷いにくくなる
  • ×ボタン・キャンセル・OK・Esc・背景クリックなど、複数の閉じ方を整理して扱える
  • 閉じた後のフォーカスや状態を自然に保てる
  • チーム全体で「Modalの安全で使いやすい閉じ方」を共有しやすくなる

業務システムにおいて、Modalは「重要な判断の場」であり、 その出口である「閉じる」の設計は、ユーザー体験と信頼性に直結します。

Modalを閉じるユーティリティは、 その出口をきちんと整えるための、とても実務的で重要な技術だと言えます。

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