こんにちは、かつコーチです。
前回はタブによるコンテンツ切り替えを実装しました。
今回は、画像の拡大表示やお知らせポップアップなどでおなじみのモーダルウィンドウを実装していきます。
モーダル自体はこれまでの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: fixed と inset: 0(top: 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'); // 元に戻す
}
body に overflow: 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キー対応やフォーカス移動など、アクセシビリティへの配慮も忘れずに行う
次に読むべき記事
次回は、スライダー・カルーセルを実装します。
タイマーを使った自動再生など、これまでより一段階複雑な状態管理が登場します。
→ 次の記事:スライダー・カルーセルを実装する