【JavaScript】モーダルウィンドウの表示・非表示を実装する

javascript-icon JavaScript

こんにちは、かつコーチです。

前回はタブによるコンテンツ切り替えを実装しました。

今回は、画像の拡大表示やお知らせポップアップなどでおなじみのモーダルウィンドウを実装していきます。

モーダル自体はこれまでのUIパーツと似た「クラスの付け外し」で作れますが、「背景を暗くする」「背景のスクロールを止める」といった、モーダル特有の注意点があります。

モーダルウィンドウとは?

モーダルの役割

モーダルウィンドウとは、元のページの上に別レイヤーとして表示される小窓のことです。

背景が薄暗くなり、その上にコンテンツが浮かび上がる、あの表示方式を見たことがある人は多いはずです。

「モーダル(modal)」は英語で「様式」「形式」を意味しますが、UIの文脈では「他の操作を受け付けない状態」というニュアンドで使われます。

つまりモーダルが開いている間は、背景の要素を操作できないようにする、というのが設計上のポイントです。

よくある用途

  • 画像をクリックすると拡大表示される「ライトボックス」
  • フォーム送信前の「本当に削除しますか?」という確認ダイアログ
  • 初回訪問時に表示されるお知らせ・クーポンのポップアップ

基本の実装手順

手順1:HTMLでモーダルの構造を作る

<button class="open-modal-btn" id="openModalBtn">お問い合わせ</button>

<div class="modal-overlay" id="modalOverlay">
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
    <button class="modal__close" id="modalCloseBtn" aria-label="閉じる">×</button>
    <h2 id="modalTitle">お問い合わせ</h2>
    <p>ご相談内容をお気軽にお送りください。</p>
    <form>
      <textarea rows="4" placeholder="お問い合わせ内容"></textarea>
      <button type="submit">送信する</button>
    </form>
  </div>
</div>

モーダル本体(.modal)と、背景を覆う .modal-overlay を分けて用意しているのがポイントです。

背景クリックで閉じる機能を実装する際、この2層構造がそのまま判定の材料になります。

role="dialog"aria-modal="true" は、スクリーンリーダーに「これはモーダルダイアログである」と伝えるための属性です。

手順2:CSSで重ね表示と初期非表示を作る

.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
}

.modal-overlay.is-open {
  display: flex;
}

.modal {
  background: #fff;
  border-radius: 8px;
  padding: 24px;
  width: 90%;
  max-width: 400px;
  position: relative;
}

.modal__close {
  position: absolute;
  top: 12px;
  right: 12px;
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  line-height: 1;
}

position: fixedinset: 0top: 0; right: 0; bottom: 0; left: 0; の省略記法)で、画面全体を覆うオーバーレイを作っています。

display: none から display: flex に切り替えることで、オーバーレイの表示・非表示と同時に、中央寄せのレイアウトも有効にしています。

手順3:JavaScriptで開閉を制御する

const openModalBtn = document.getElementById('openModalBtn');
const modalCloseBtn = document.getElementById('modalCloseBtn');
const modalOverlay = document.getElementById('modalOverlay');

function openModal() {
  modalOverlay.classList.add('is-open');
}

function closeModal() {
  modalOverlay.classList.remove('is-open');
}

openModalBtn.addEventListener('click', openModal);
modalCloseBtn.addEventListener('click', closeModal);

// オーバーレイ自体がクリックされたら閉じる(モーダル本体のクリックは除く)
modalOverlay.addEventListener('click', (event) => {
  if (event.target === modalOverlay) {
    closeModal();
  }
});

event.target === modalOverlay という判定がポイントです。

modalOverlay の内側には .modal も含まれているため、モーダル本体をクリックしたときも click イベント自体は modalOverlay まで伝わってきます(バブリング)。

ここで「クリックされた要素そのもの(event.target)が modalOverlay 自身かどうか」を確認することで、「モーダルの外側(背景部分)がクリックされたときだけ閉じる」という動きを実現しています。

つまずきやすいポイント:背景がスクロールできてしまう

私が実際にハマった経験

私が最初にモーダルを実装したとき、スマホで確認してみると、モーダルを開いた状態でも指を動かすと背景のページがスクロールしてしまう、という不具合に気づきました。

PCではマウスホイールを操作しない限り気づきにくいのですが、スマホだと背景が動いてしまうことにすぐ気づかれてしまい、「モーダルの裏で何が起きているの?」と違和感を持たれる原因になります。

モーダルを開いている間は、背景を固定してスクロールできないようにするのがマナーです。

❌ Before:モーダルを開いても背景がスクロールできてしまう

function openModal() {
  modalOverlay.classList.add('is-open');
}
// bodyの状態を何も変えていないため、背景を指でなぞるとページがスクロールする

✅ After:body にスクロール禁止用のクラスを付ける

body.is-modal-open {
  overflow: hidden;
}
function openModal() {
  modalOverlay.classList.add('is-open');
  document.body.classList.add('is-modal-open'); // 背景のスクロールを禁止
}

function closeModal() {
  modalOverlay.classList.remove('is-open');
  document.body.classList.remove('is-modal-open'); // 元に戻す
}

bodyoverflow: hidden を付けると、ページ全体のスクロールがロックされます。

モーダルを開くタイミングでこのクラスを付け、閉じるタイミングで必ず外す、という対になった処理を忘れないようにしてください。

「開く処理」だけ書いて「閉じたときに元に戻す処理」を書き忘れる、というのはこの手のUIで非常によくあるミスなので、開閉のペアで必ずセットで確認する習慣をつけると防げます。

応用:ESCキーとフォーカス管理

ESCキーで閉じる

ハンバーガーメニューのときと同様、モーダルもESCキーで閉じられるようにしておくとキーボード操作に配慮できます。

document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape' && modalOverlay.classList.contains('is-open')) {
    closeModal();
  }
});

開いた瞬間にモーダル内へフォーカスを移す

アクセシビリティを意識するなら、モーダルを開いた瞬間にフォーカス(キーボード操作の起点)をモーダル内の要素に移すことも重要です。

function openModal() {
  modalOverlay.classList.add('is-open');
  document.body.classList.add('is-modal-open');
  modalCloseBtn.focus(); // フォーカスを閉じるボタンに移す
}

focus() メソッドを使うと、指定した要素にフォーカスを移動できます。

これを書いておかないと、モーダルを開いてTabキーを押したときに、背景側の要素にフォーカスが残ったままになり、キーボードだけで操作しているユーザーが混乱してしまいます。

jQueryではこう書けます(参考)

$('#openModalBtn').on('click', function () {
  $('#modalOverlay').addClass('is-open');
  $('body').addClass('is-modal-open');
});

$('#modalCloseBtn').on('click', function () {
  $('#modalOverlay').removeClass('is-open');
  $('body').removeClass('is-modal-open');
});

考え方自体は素のJavaScript版と同じで、addClass / removeClass に置き換わるだけです。

詳しい解説は別カテゴリのjQuery記事で扱います。

まとめ

この記事のポイント

  • モーダルは「オーバーレイ」と「モーダル本体」の2層構造で作ると、背景クリックでの判定がしやすい
  • event.target を使うと「クリックされたのが要素自身か、子要素か」を判定できる
  • モーダルを開いたら body のスクロールを止め、閉じたら必ず元に戻す
  • ESCキー対応やフォーカス移動など、アクセシビリティへの配慮も忘れずに行う

次に読むべき記事

次回は、スライダー・カルーセルを実装します。

タイマーを使った自動再生など、これまでより一段階複雑な状態管理が登場します。

→ 次の記事:スライダー・カルーセルを実装する

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