JavaScriptでモーダルウィンドウを作る方法ですが、1年前に書いた記事を今回大幅にリニューアルいたしました!
モーダルウィンドウに適した構造の作り方から、JavaScriptの設置のコツなど、この記事でモーダルウィンドのことはすべて完結させようと思います。
モーダルウィンドウといっても要はフェードイン・フェードアウトでボックスを表示させるだけなのですが、意外と作っていこうと思うとどのように作ったらいいのか?迷うことも多いのではないでしょうか?
モーダルウィンドウはHTMLでどういうふうに組んだらいいんだろう?フェードイン・フェードアウトの方法も教えてほしい!
今回、このような疑問を解決しつつ細かに説明していきます。
僕は現在、コーダーとディレクションで年間50サイトほど手掛けている現役のプログラマーになります。その経験で得たノウハウをこの記事でお伝えできたらありがたいと思ってます!
さっそくモーダルウィンドを作っていきましょう!
そもそも、モーダルウィンドウってどういう状況で使われているのか?というと、、
・注意事項のテキスト
・エラー発生時のテキスト
・必ず読んでほしいテキストなど、
上記のように、読んでほしい細かなテキストなどの表示をする特徴があります。なのでモーダル周りの背景は強制的にクリックできないようにしておき、閉じるボタンを押さないと消えないという特徴があります。
細かなテキストをサイトに載せつつ、シンプルなUIも両立できるという現在もモーダルウィンドウはよく使われる手法になります。
それではモーダルウィンドウを作っていきたいと思います。
完成サンプル動画
最終的には3つのモーダルを表示するようにしたいと思います。
今回のファイル構造
ファイル構造はいたってシンプルです。
それでは構造となるHTMLを書いていきましょう。
モーダルウィンドウのHTML(構造)を作る
まずは完成HTMLをこちらに貼りますね。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modal Sample</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul>
<li><a href="" class="" data-modal="modal001">モーダル001</a></li>
<li><a href="" class="" data-modal="modal002">モーダル002</a></li>
<li><a href="" class="" data-modal="modal003">モーダル003</a></li>
</ul>
<!-- modal start -->
<div class="modal">
<div class="modal__bg" data-modal="bg"></div>
<div class="modal__inner" data-modal="inner">
<!-- modal001 start -->
<div class="modal-card" data-modal="box-modal001">
<div class="modal-card__close" data-modal="close">
<div class="modal-card__closeBtn"></div>
</div>
<p class="modal-card__heading">modal001</p>
<p class="modal-card__text">モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。</p>
</div>
<!-- modal001 end -->
</div>
</div>
<!-- modal end -->
<script src="index.js"></script>
</body>
</html>
①モーダルを発動させるボタン
②モーダルパーツの一番外枠(モーダルが増えてもそのまま)
③モーダルのグレーの背景パーツ(モーダルが増えてもそのまま)
④モーダルパーツを囲うボックス(モーダルが増えてもそのまま)
⑤モーダルのメインパーツ(モーダルを増やす場合、このパーツを複製していく)
上記をみてもらってもわかると思いますが、大外にmodalクラスで囲っておき、modalクラス内で背景と、モーダルパーツに分離していることがわかります。
なぜ、そうするかというと、モーダルが複数の場合はモーダルパーツ内にグレー背景を入れておくと、何度も同じ記述を書かないといけないことになるので、初めから分離しておくとグレー背景を何度も書かなくてもいいので、この構造にしています。
あと、JavaScriptを設置する箇所はdata属性を設置しておきます。クラスと分けてJSの部分はdata属性にしておくと、後々わかりやすいです。
JSファイルの読み込みはbodyの閉じタグの手前に配置してください。理由はHTMLが読み込まれる前にJSファイルを読み込むとHTMLが無いといわれてエラーになるので注意しましょう。
現状の表示は下記画像のようになっていれば大丈夫です!
次からはCSSを追加していきます。
モーダルウィンドウのスタイルを付けていこう
CSSは下記の感じになります。
.modal__bg {
position: fixed;
z-index: 10009;
top: 0;
left: 0;
width: 200%;
height: 200%;
background: rgba(0, 0, 0, 0.5);
opacity: 0; /* 初期状態で非表示 */
visibility: hidden; /* 初期状態で非表示 */
transition: opacity .6s ease, visibility .6s ease;
}
.modal__bg.is-active {
opacity: 1; /* 非表示を解除 */
visibility: visible; /* 非表示を解除 */
}
.modal__inner {
position: fixed;
z-index: 10010;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
margin: auto;
padding: 0 10px;
opacity: 0; /* 初期状態で非表示 */
visibility: hidden; /* 初期状態で非表示 */
transition: opacity .6s ease, visibility .6s ease;
}
.modal__inner.is-active {
opacity: 1; /* 非表示を解除 */
visibility: visible; /* 非表示を解除 */
}
.modal-card {
width: 290px;
min-height: 290px;
background: #fff;
filter: drop-shadow(0 0 10px rgba(0 ,0, 0, 0.4));
border-radius: 16px;
padding: 16px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 24px;
position: absolute;
z-index: 1;
transform: translate3d(0, 48px, 0);
opacity: 0; /* 初期状態で非表示 */
visibility: hidden; /* 初期状態で非表示 */
transition: opacity .6s ease, visibility .6s ease, transform .3s ease-in-out;
}
.modal-card.is-active {
position: relative;
z-index: 2;
opacity: 1; /* 非表示を解除 */
visibility: visible; /* 非表示を解除 */
transform: translate3d(0, 0, 0);
}
.modal-card__heading {
margin: 0;
font-size: 18px;
line-height: 1.6;
font-weight: bold;
color: #444;
}
.modal-card__text {
margin: 0;
font-size: 16px;
line-height: 1.6;
color: #444;
}
.modal-card__close {
width: 15px;
height: 15px;
position: absolute;
top: 16px;
right: 13px;
}
.modal-card__close::before,
.modal-card__close::after {
content: "";
position: absolute;
display: inline-block;
top: 50%;
left: 50%;
width: 18px;
height: 3px;
background: #000;
cursor: pointer;
}
.modal-card__close::before {
transform: translate(-50%, -50%) rotate(45deg);
}
.modal-card__close::after {
transform: translate(-50%, -50%) rotate(-45deg);
}
上記のようにCSSを書いていきましょう。こちらモーダルを増やしてもこのままのCSSで触らなくても大丈夫なように組んであります。
・.modal__bgクラスで画面全体をグレーの背景で覆うように設定
・opacity:0;とvisibility:hidden;で背景とモーダルパーツを初期状態を非表示にしておく
・transitionでopacityとvisibilityにアニメーションを設置。
・display:flex;とposition:absolute;でモーダルを中央配置にする。
ポイントはCSSでフェードインとフェードアウトのアニメーションを設置しているのですが、transitionにvisibilityを付けてアニメーションの記述をしておくことです。
visibilityはdispalyと同じような動きをするCSSプロパティですが、displayはtransitionが効かないです。
visibilityはtransitionが効くので、フェードイン・フェードアウトがCSSで再現できます。visibilityにもしっかりとtransitionを設置しておきましょう。
みてもらうとわかるかもですが、アニメーション後のスタイルは.is-activeというクラスで設置しております。
こちらで一度表示を確認しておきましょう。
上記画像のようにモーダル部分の表示が消えていればOKです。
次は、モーダル出現ボタンをクリックした時に.is-activeクラスを付加するようにJavaScriptで機能追加していきます。
JavaScriptでモーダルに動きを付けていきましょう
次はCSSにも書いた通り、.is-activeクラスをJavaScriptで付加していきます。
まずは必要なHTMLをJavaScriptで取得していきましょう。
'use strict';
const modal001Button = document.querySelector('[data-modal="modal001"]');
console.log(modal001Button);
const modalCloseBtns = document.querySelectorAll('[data-modal="close"]');
console.log(modalCloseBtns);
・document.querySelector() クラスやdata属性を指定し、document配下の初めの一つだけのHTMLを取得
・document.querySelectorAll() クラスやdata属性を指定し、document配下のすべてのHTMLを取得
・モーダルを出現させるボタン
・モーダルを消すボタン
まずは上記2つのボタンをそれぞれ取得して変数に入れておきます。
データ属性で要素を取得するときは角かっこ[]で囲う必要がありますので、気をつけてください。
モーダルを消すボタン[data-modal="close"]はモーダルを増やした時に複数になるので、querySelectorAll()メソッドを使い複数を取得できるようにしておきます。
それぞれconsole.log()でデバックしておきます。
ブラウザのデベロッパーツールを開いて「コンソール」をクリックしてみましょう。下記のように要素が2つ取得できていて、エラーが出てないなら大丈夫です。
コンソールでちゃんと取得できているか?確認できたら次はモーダル部分の動きを実装します。先ほどの下に続きで書いていきます。
'use strict';
const modal001Button = document.querySelector('[data-modal="modal001"]');
console.log(modal001Button);
const modalCloseBtns = document.querySelectorAll('[data-modal="close"]');
console.log(modalCloseBtns);
/* モーダルを表示 */
modal001Button.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector('[data-modal="bg"]').classList.add('is-active');
document.querySelector('[data-modal="inner"]').classList.add('is-active');
document.querySelector('[data-modal="box-modal001"]').classList.add('is-active');
});
・addEventListener('イベント名', 関数) HTMLにイベントを付加し、イベント発生後に関数を実行する
・preventDefault() aタグのデフォルトの挙動(画面推移)をキャンセル
・classList.add() 指定したクラスを付加する
/* モーダルを表示 */の部分のコードを追加しました。
先ほど取得したモーダルを出現するボタンを入れた変数modal001ButtonにaddEventListenerでイベントを付けていきます。付けるイベントはもちろんclickイベントとなります。(これでクリックができるようになり、クリック後の処理をつけることができる)
クリック後の処理(関数)は、グレーの背景[data-modal="bg"]、モーダルの外枠[data-modal="inner"]、モーダルパーツ[data-modal="box-modal001"]にそれぞれclassList.add()メソッドを使いis-activeクラスを付加するように設定しております。
これでモーダルを表示する動作部分は完了になります。こちらで一度表示を確認してみましょう。
上のようにモーダルがクリックで表示されればOKです。ちゃんとフェードアニメーションになってましたね!クローズボタンは動作していないことも確認しておきます。
次はクローズボタンの動きを作っていきます。
'use strict';
const modal001Button = document.querySelector('[data-modal="modal001"]');
console.log(modal001Button);
const modalCloseBtns = document.querySelectorAll('[data-modal="close"]');
console.log(modalCloseBtns);
/* モーダルを消す動作 */
modalCloseBtns.forEach(modalCloseBtn => {
modalCloseBtn.addEventListener('click', function() {
document.querySelector('[data-modal="bg"]').classList.remove('is-active');
document.querySelector('[data-modal="inner"]').classList.remove('is-active');
document.querySelector('[data-modal="box-modal001"]').classList.remove('is-active');
});
});
/* モーダルを表示 */
modal001Button.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector('[data-modal="bg"]').classList.add('is-active');
document.querySelector('[data-modal="inner"]').classList.add('is-active');
document.querySelector('[data-modal="box-modal001"]').classList.add('is-active');
});
・forEach() 繰り返し処理。配列から複数あるHTML要素を1つずつ取り出して処理する
・classList.remove() 指定したクラスがあれば、そのクラスを削除する
/* モーダルを消す動作 */の箇所にコードを追記ました。
モーダルを消す動作部分はなにをしているかというと、querySelectorAll()で取得した要素は複数(現在は1つですが、)になるので、繰り返し処理(forEach)で複数のクローズボタンにまとめてクリックイベントを設置しております。そしてクリック後の処理はis-activeクラスを外す処理を入れてます。
モーダルを消すボタンを入れた変数modalCloseBtnsは複数のモーダルに対応するためにquerySelectorAll()で取得しました。querySelectorAll()は配列のような状態で取得するため、まずはその配列の状態から1つずつクローズボタンを取り出す必要があります。
querySelectorAll()で取得した要素はforEach()で取り出します。forEachはforEach(modalCloseBtn)という感じで第一引数に新たに変数を定義することができます。
その変数(modalCloseBtn)に一つずつ取り出したクローズボタンが入るという仕組みになります。
forEach()で1つずつクローズボタンを取り出したら、モーダルを表示と同じようにaddEventListener()でクリックイベントを付加しています。
クリックした後の処理(関数)はclassList.remove()というメソッドでis-activeクラスをHTMLから削除します。
forEachの繰り返し処理が入ってややこしいですが、下記画像のようなイメージになります。
今回、querySelectorAllの要素取得についてざっくりの説明でしたが、下記リンクで詳しく解説しておりますので、詳しく知りたいなら読んでみてください。
これでもうクローズボタンが機能していると思うので、一度表示を確認してみましょう。
モーダル出現後、クローズボタンをクリックしてフェードアニメーションでモーダルが消えていることが確認できました!
アニメーションがちゃんと動かない場合は、デベロッパーツールでコンソールを確認してエラーが出てないか?をみてみましょう。
これでモーダルの設置は完了ですが、モーダルを複数設置することもありかもですので、次はモーダルを複数設置してみましょう。
複数のモーダルを設置し動作させよう
もう複数設置する用のモーダルの構造(HTML)になっているので、まずはモーダル部分のHTMLを複製していきます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modal Sample</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<ul>
<li><a href="" class="" data-modal="modal001">モーダル001</a></li>
<li><a href="" class="" data-modal="modal002">モーダル002</a></li>
<li><a href="" class="" data-modal="modal003">モーダル003</a></li>
</ul>
<!-- modal start -->
<div class="modal">
<div class="modal__bg" data-modal="bg"></div>
<div class="modal__inner" data-modal="inner">
<!-- modal001 start -->
<div class="modal-card" data-modal="box-modal001">
<div class="modal-card__close" data-modal="close">
<div class="modal-card__closeBtn"></div>
</div>
<p class="modal-card__heading">modal001</p>
<p class="modal-card__text">モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。</p>
</div>
<!-- modal001 end -->
<!-- modal002 start -->
<div class="modal-card" data-modal="box-modal002">
<div class="modal-card__close" data-modal="close">
<div class="modal-card__closeBtn"></div>
</div>
<p class="modal-card__heading">modal002</p>
<p class="modal-card__text">モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。</p>
</div>
<!-- modal002 end -->
<!-- modal003 start -->
<div class="modal-card" data-modal="box-modal003">
<div class="modal-card__close" data-modal="close">
<div class="modal-card__closeBtn"></div>
</div>
<p class="modal-card__heading">modal003</p>
<p class="modal-card__text">モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。モーダルの内容です。</p>
</div>
<!-- modal003 end -->
</div>
</div>
<!-- modal end -->
<script src="index.js"></script>
</body>
</html>
・赤枠部分を複製
・青枠部分の数字を変更する
・data属性の部分を忘れずに変更(一番重要)
まずはHTML部分ですが、上記に書いてある通りに複製して、番号の部分の数字を002に変更します。3個目のモーダルは003になります。
HTMLは以上になります。
次はJavaScriptの部分を複製していきます。
'use strict';
const modal001Button = document.querySelector('[data-modal="modal001"]');
console.log(modal001Button);
const modal002Button = document.querySelector('[data-modal="modal002"]');
console.log(modal002Button);
const modal003Button = document.querySelector('[data-modal="modal003"]');
console.log(modal003Button);
const modalCloseBtns = document.querySelectorAll('[data-modal="close"]');
console.log(modalCloseBtns);
/* モーダルを消す動作 */
modalCloseBtns.forEach(modalCloseBtn => {
modalCloseBtn.addEventListener('click', function() {
document.querySelector('[data-modal="bg"]').classList.remove('is-active');
document.querySelector('[data-modal="inner"]').classList.remove('is-active');
document.querySelector('[data-modal="box-modal001"]').classList.remove('is-active');
document.querySelector('[data-modal="box-modal002"]').classList.remove('is-active');
document.querySelector('[data-modal="box-modal003"]').classList.remove('is-active');
});
});
/* モーダルを表示 */
modal001Button.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector('[data-modal="bg"]').classList.add('is-active');
document.querySelector('[data-modal="inner"]').classList.add('is-active');
document.querySelector('[data-modal="box-modal001"]').classList.add('is-active');
});
modal002Button.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector('[data-modal="bg"]').classList.add('is-active');
document.querySelector('[data-modal="inner"]').classList.add('is-active');
document.querySelector('[data-modal="box-modal002"]').classList.add('is-active');
});
modal003Button.addEventListener('click', function(e) {
e.preventDefault();
document.querySelector('[data-modal="bg"]').classList.add('is-active');
document.querySelector('[data-modal="inner"]').classList.add('is-active');
document.querySelector('[data-modal="box-modal003"]').classList.add('is-active');
});
すいません。少し画像が小さいですが、3箇所の赤枠部分を複製してそれぞれ3つずつに増やします。
・モーダル発動するボタン部分を複製して数字部分を変更
・モーダルを消す動作の中のis-activeクラスを削除している部分を複製して数字を変更
・モーダルの表示部分は全てを複製して、数字部分は全て変更。
CSSは触らないで、HTMLとJSの複製で複数のモーダルを出現させる仕様に変更できました。
こちらモーダルの出現部分のボタンを取得部分を複製して、モーダルを消す部分の箇所を複製、モーダルの出現箇所を複製で完了です。
こちらで一度、ちゃんと動作するか確認してみましょう。
あっさり複数のモーダルを動作させることができました。
これも複数のモーダルを動かす前提でHTMLやCSS、JavaScriptを組んでいたおかけですね。
今回はモーダルの作り方はこれで完了となります。
もしわからないことがありましたら、こちらからお問い合わせしていただければありがたいです。
今回のサンプルデータはダウンロードできるようにもしておきますので、よろしければこちらでも確認してみてください。
モーダルウィンドウの使うタイミングを極める
それではモーダルウィンドウの作り方もわかったので、次はどういう状況でモーダルウィンドウを設置すればいいのかわからないかもしれないので、そこについて解説いたします。
モーダルウィンドウの使うタイミングはズバリ、スマホサイズのボックスコンテンツです。
PCサイズではなんとか収まっていたテキスト量ですが、スマホサイズになると収まりきらず、スマホサイズでボックスコンテンツが縦に伸びてしまって嵩が出てしまう..という時にモーダルウィンドウを設定するといい感じでコンテンツが収まります。
例えば、今回のデザインでいうとボックスコンテンツにしてスマホで収めるとこんな感じになります。
このようにテキスト量の多いボックスコンテンツもモーダルにすると2カラムで収まってくれます。これでボックスにアイコンでも入れておくとめちゃいい感じになります。
1カラムでテキスト多くて縦長になるならモーダルウィンドウに収めた方がUI的にスッキリするし、CSSを組むとモーダル内でもスクロール仕様にもできます。
サイト制作者で、もしスマホデザインはお任せとなった時に、こういう状況でスマホサイズでテキストが多いボックスコンテンツをモーダルウィンドウにするとドヤ顔できるかもです(採用するかはお客さんに寄るかもですが...)。
こんな状況で、状況を見極めてモーダルウィンドウを使ってみてください。
モーダルウィンドウはjQueryよりもJavaScriptがおすすめ
モーダルウィンドウをJavaScriptで書くのとjQueryで書くのとそんなにコード量は変わらないので、どうせ書くならJavaScriptがいいかもです。
jQueryでは便利なアニメーションを含んだメソッドがあるので、作るものによってjQueryで作ると数行のコードで実装できます。
例えばアコーディオンなどはスライドアニメーションを含んだメソッドがあるので、jQueryだと速攻で作れます。
モーダルウィンドウはあまりjQueryの恩赦を受け取れるメソッドがあまりないので、JavaScriptでどうせだったら書いてしまった方がいいかもです。
フェード系アニメーションの実装はJavaScriptでサラッと書けますので、一度トライしてみてください。
最後までお読みいただきまして誠にありがとうございました!
モーダルウィンドウの最新記事を書きました。HTMLのみでモーダルを複製できるコードです。