ページ内に複数設置できるモーダル(ポップアップ)ウィンドウをJavaScriptで実装してみる(備忘録)
今回はJavaScriptを用いて、ページ内に複数設置できるモーダル(ポップアップ)ウィンドウを実装してみる。
利用しやすいように、今回はモーダル(ポップアップ)ウィンドウが1つの場合でも、2つ以上の場合でも使えるよう対応している。
また、モーダル(ポップアップ)ウィンドウの中身が、多くなった場合でもスクロールして中身を確認できるように対応している。
その他、ポップアップの切り替わりは、ボタンを押すとふわっと現れ、モーダル(ポップアップ)ウィンドウ以外の部分や×ボタンを押すとふわっと閉じる仕様になっている。
それでは解説に入っていく。
htmlの記述について
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>モーダル(ポップアップ)ウィンドウ</title>
<link rel="stylesheet" href="../reset.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<main>
<section>
<div class="ly_inner" id="menu1">
<h2>menu1</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<!-- モーダル(ポップアップ)ボタン -->
<p class="js_modalBtnWrap"><a href="" class="js_modalBtnCont" data-modal-btn="modal01">こちらを<span class="hp_pc">クリック</span><span class="hp_sp">タップ</span></a></p>
<!-- //モーダル(ポップアップ)ボタン -->
</div>
<!-- モーダル(ポップアップ)の内容 -->
<div class="js_modalWrap" data-modal-cont="modal01">
<div class="js_modalBG"></div>
<div class="js_modalContInner">
<span class="js_modalClose"></span>
<div class="js_modalCont">
<p><img src="img_sample01.jpg" alt=""></p>
<p>テキストテキスト</p>
</div>
</div>
</div>
<!-- //モーダル(ポップアップ)の内容 -->
</div>
</section>
<section>
<div class="ly_inner" id="menu2">
<h2>menu2</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<!-- モーダル(ポップアップ)ボタン -->
<p class="js_modalBtnWrap"><a href="" class="js_modalBtnCont" data-modal-btn="modal02">中身が多い場合</a></p>
<!-- //モーダル(ポップアップ)ボタン -->
</div>
<!-- モーダル(ポップアップ)の内容 -->
<div class="js_modalWrap" data-modal-cont="modal02">
<div class="js_modalBG"></div>
<div class="js_modalContInner">
<span class="js_modalClose"></span>
<div class="js_modalCont">
<p><img src="img_sample01.jpg" alt=""></p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p><img src="img_sample01.jpg" alt=""></p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
</div>
</div>
</div>
<!-- //モーダル(ポップアップ)の内容 -->
</div>
</section>
<section>
<div class="ly_inner" id="menu3">
<h2>menu3</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<!-- モーダル(ポップアップ)ボタン -->
<p class="js_modalBtnWrap"><a href="" class="js_modalBtnCont" data-modal-btn="modal03">色々入れてみる</a></p>
<!-- //モーダル(ポップアップ)ボタン -->
</div>
<!-- モーダル(ポップアップ)の内容 -->
<div class="js_modalWrap" data-modal-cont="modal03">
<div class="js_modalBG"></div>
<div class="js_modalContInner">
<span class="js_modalClose"></span>
<div class="js_modalCont">
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<p>テキストテキスト</p>
<table>
<tr>
<th>見出し</th>
<td>内容が入ります内容が入ります</td>
</tr>
<tr>
<th>見出し</th>
<td>内容が入ります内容が入ります</td>
</tr>
<tr>
<th>見出し</th>
<td>内容が入ります内容が入ります</td>
</tr>
</table>
<p><img src="img_sample01.jpg" alt=""></p>
<p class="md_linkBtnWrap"><a href="" class="md_linkBtnCont">リンクも設置可</a></p>
</div>
</div>
</div>
<!-- //モーダル(ポップアップ)の内容 -->
</div>
</section>
<section>
<div class="ly_inner" id="menu4">
<h2>menu4</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
</div>
</div>
</section>
<section>
<div class="ly_inner" id="menu5">
<h2>menu5</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
</div>
</div>
</section>
</main>
<script src="style.js"></script>
</body>
</html>
モーダル(ポップアップ)ボタンについて
<!-- モーダル(ポップアップ)ボタン -->
<p class="js_modalBtnWrap"><a href="" class="js_modalBtnCont" data-modal-btn="modal01">こちらを<span class="hp_pc">クリック</span><span class="hp_sp">タップ</span></a></p>
<!-- //モーダル(ポップアップ)ボタン -->
CSSやJavaScriptで見た目や挙動を調整するために、classをつけているが、注目すべき点は、「data-modal-btn=”modal01”」の部分だ。
1つ目のボタンにはmodal01、2つ目のボタンにはmodal02と連番で名前をつけていくことがポイントだ。別にmodal以外の任意の名前でつけることも可能だ。
また、このdata-modal-btnの「modal-btn」部分も任意の名前でつけることができる。詳しくは調べてみるといいだろう。
モーダル(ポップアップ)の内容について
<!-- モーダル(ポップアップ)の内容 -->
<div class="js_modalWrap" data-modal-cont="modal01">
<div class="js_modalBG"></div>
<div class="js_modalContInner">
<span class="js_modalClose"></span>
<div class="js_modalCont">
<p><img src="img_sample01.jpg" alt=""></p>
<p>テキストテキスト</p>
</div>
</div>
</div>
<!-- //モーダル(ポップアップ)の内容 -->
js_modalWrapで囲まれている部分が全体の中身になる。ここでもdata-modal-cont=”modal01”を設定している。
これが実はポイントとなっていて、data-modal-cont=”modal01”の「modal01」については、先ほどのボタンで設定したdata-modal-btn=”modal01”の「modal01」と名称を一緒にさせておく必要がある。後ほどjavascript側で設定するためだ。
また、data-modal-contの「modal-cont」部分は任意の名前で設定していただいてもOKだ。
2つ目以降のボタンやモーダル(ポップアップ)の中身の作成について
2つ目以降を準備する場合は、1つ目に作成したソースをコピペして、data-modal-btnとdata-modal-contの値をmodal02と設定すればOKだ。
ただ、今回のソースでは、idやmenuなどのテキスト部分も変える必要があるが、実際には自分でカスタマイズしやすいように対応いただければよいと思う。
cssの記述について
@charset "utf-8";
/* ==========================
初期設定
========================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
img {
width: 100%;
}
/* ==========================
モーダル(ポップアップ)
========================== */
.js_modalBtnWrap {
width: 300px;
max-width: 100%;
margin: 0 auto;
text-align: center;
}
.js_modalBtnCont {
display: block;
color: #222;
background-color: #fff;
padding: 15px 5px;
transition: 0.3s;
}
.js_modalBtnCont:hover,
.js_modalBtnCont:focus {
color: #fff;
background-color: #222;
}
.js_modalWrap {
opacity: 0;
z-index: -1;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.js_modalWrap.active {
animation: modalOpen 0.3s ease forwards;
}
.js_modalWrap.active2 {
animation: modalClose 0.3s ease forwards;
}
.js_modalBG {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6);
}
.js_modalContInner {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
width: 720px;
max-width: 100%;
background-color: #fff;
max-height: 600px;
padding: 40px;
}
.js_modalCont {
overflow-y: scroll;
max-height: 520px;
}
.js_modalCont > * + * {
margin-top: 10px;
}
.js_modalContInner > .js_modalClose + * {
margin-top: 0;
}
.js_modalClose {
display: block;
position: absolute;
top: 10.5px;
right: 10.5px;
width: 20px;
height: 20px;
overflow: hidden;
cursor: pointer;
z-index: 1001;
}
.js_modalClose::before,
.js_modalClose::after {
content: "";
background-color: #222;
position: absolute;
top: -5px;
right: 10px;
width: 1px;
height: 30px;
}
.js_modalClose::before {
transform: rotate(45deg);
}
.js_modalClose::after {
transform: rotate(-45deg);
}
@keyframes modalOpen {
0% {
opacity: 0;
}
100% {
opacity: 1;
z-index: 1000;
}
}
@keyframes modalClose {
0% {
opacity: 1;
z-index: 1000;
}
100% {
opacity: 0;
}
}
/* ==========================
コンテンツの中身
========================== */
.ly_inner {
width: 100%;
max-width: 1080px;
margin: 100px auto;
padding: 40px;
background-color: #ccc;
}
.ly_inner h2 {
font-size: 150%;
font-weight: bold;
margin-bottom: 30px;
}
.md_textblock > * + * {
margin-top: 10px;
}
.md_textblock > * + .js_modalBtnWrap {
margin-top: 20px;
}
table {
width: 100%;
}
table th,
table td {
border: 1px solid #222;
padding: 10px;
}
table th {
width: 20%;
background-color: #ccc;
white-space: nowrap;
}
.md_linkBtnWrap {
width: 300px;
max-width: 100%;
margin: 10px auto 0;
text-align: center;
}
.md_linkBtnCont {
display: block;
color: #222;
border: 1px solid #222;
padding: 15px 5px;
transition: 0.3s;
}
.md_linkBtnCont:hover,
.md_linkBtnCont:focus {
color: #fff;
background-color: #222;
}
/* PC幅に適用 */
@media (min-width: 768px) {
.hp_sp {
display: none !important;
}
}
/* スマホ幅に適用 */
@media (max-width: 767px) {
.hp_pc {
display: none !important;
}
.js_modalContInner {
max-height: 400px;
padding: 20px;
}
.js_modalCont {
max-height: 360px;
}
.js_modalClose {
top: -30px;
}
.js_modalClose::before,
.js_modalClose::after {
background-color: #fff;
}
}
モーダル(ポップアップ)ウィンドウの中身について
js_modalWrapについて
js_modalWrapで画面全体の範囲まで表示されるよう調整しているが、ボタンをクリックしたら実際に表に表示されるようにするため、最初の時点では、opacityで透明にして、さらにz-indexで裏側に隠れるように対応している。
javascript側でボタンをクリックしたらhtml側にactiveのクラスが付与されるので、あとはこの時にkeyframesのアニメーションが動くよう実装すれば、ふわっと表示される表現が可能になる。
また、モーダル(ポップアップ)ウィンドウが表示されてから、モーダル(ポップアップ)ウィンドウ以外の部分や×ボタンで閉じる動作をしたときに、active2のクラスが付与され、0.3秒後に同クラスが削除される設定にしており、ここでもkeyframesのアニメーションでふわっと閉じる表現を実現した。
js_modalBGについて
モーダル(ポップアップ)ウィンドウが表示された時の、中身の周囲の黒っぽい透過された箇所のことを指している。
実装自体は難しくはないのだが、html側の記載する場所には注意しておきたいところだ。
どんな実装をやりたいかによっても対応は変わってくるのだが、js_modalContInnerの親要素や子要素、兄弟要素に対して指定するのがいいのかといったことを考えながら組んでいくと、できない場合の経験もできて勉強になると思う。今回の場合だと、兄弟要素で対応している。
js_modalContInner、js_modalCont、js_modalCloseについて
js_modalContInnerについては、上下左右中央配置とmax-height、overflow: hidden;がポイントになっている。max-heightによって、モーダル(ポップアップ)ウィンドウの中身の多い少ないによって、表示領域をコントロールすることができるようになる。
js_modalContは実際にはなくてもいいのだが、モーダル(ポップアップ)ウィンドウの中身の要素が多い時に、スクロールさせて表示させるのに、見た目を調整している。
overflow-y: scroll;を使うときは、高さの指定がないと効かないことには注意したい。ここでは、paddingの値も計算に入れて、PC幅で例えると、600-80=520pxで設定した。
js_modalCloseについては、×ボタンの部分を実装している。×の外枠の大きさを決めて、そこから擬似要素を使って線を作成している。
JavaScriptの記述について
'use strict';
// モーダル(ポップアップ)
const modalBtn = document.querySelectorAll('.js_modalBtnCont');
const modalWindow = document.querySelectorAll('.js_modalWrap');
const modalClose = document.querySelectorAll('.js_modalClose');
const modalBG = document.querySelectorAll('.js_modalBG');
window.addEventListener('DOMContentLoaded', function () {
for (let i = 0; i < modalBtn.length; i++) {
modalBtn[i].addEventListener('click', function (e) {
e.preventDefault();
let dataModalBtn = modalBtn[i].getAttribute('data-modal-btn');
for (let j = 0; j < modalWindow.length; j++) {
if (modalWindow[j].getAttribute('data-modal-cont') === dataModalBtn) {
modalWindow[j].classList.add('active');
}
}
})
modalBG[i].addEventListener('click', function () {
modalWindow[i].classList.add('active2');
setTimeout(function() {
modalWindow[i].classList.remove('active');
modalWindow[i].classList.remove('active2');
}, 300);
})
modalClose[i].addEventListener('click', function () {
modalWindow[i].classList.add('active2');
setTimeout(function() {
modalWindow[i].classList.remove('active');
modalWindow[i].classList.remove('active2');
}, 300);
})
}
});
constで変数を定義
ボタン部分(modalBtn)、モーダル(ポップアップ)ウィンドウの大枠部分(modalWindow)、×ボタン部分(modalClose)、黒透過背景部分(modalBG)を各種定義している。
今回は複数のモーダル(ポップアップ)ウィンドウの実装になるため、getElementByIdは使用していない。
for文で繰り返し処理を実行
modalBtnをクリックしたときに、イベントが発火する。このmodalBtnはaリンクで対応しているので、リンク遷移させないために、e.preventDefaultで対応している。
続いて、letで変数を定義している。ここではconstは使用できないことに注意したい。for文で回しているので値が変化していくためだ。
ここでgetAttributeによりhtmlの属性値を取得できるようになる。今回の場合は、1回目のfor文が回っているときに、data-modal-btnの「modal01」を取得する。もちろん2回目は「modal02」を取得する。
第2のfor文と、if文を用いてさらに条件を定義していく
modalBtnをクリックしたときに、どこのモーダル(ポップアップ)ウィンドウを表示するかを定義するために、再度for文で回していくことが必要になるのだが、modalBtnをクリックという動作と、モーダル(ポップアップ)ウィンドウを表示する動作というのは別物になるので、for文で「i」と指定していたときは別の値を指定して対応することが必要になってくる。
今回のfor文では「j」を使っている。そしてこのfor文の中で、html側で設定していたdata-modal-btnとdata-modal-contの属性値が一致するときに、該当のモーダル(ポップアップ)ウィンドウが表示されるように定義していく。
「modalWindow[j].getAttribute(‘data-modal-cont’) === dataModalBtn)」の記述部分で属性値の一致を判断している。
modalWindow[j]で1回目のfor文が回り、dataModalBtnの1回目の属性値「modal01」と一致しているかを確認し、一致していればmodalWindowに対してactiveのクラスが付与され、ふわっとモーダル(ポップアップ)ウィンドウが表示されるようになる。
modalBG[i]、modalClose[i]について
モーダル(ポップアップ)ウィンドウが表示されているときに、閉じるための記述である。
閉じる時もふわっと閉じるために、該当要素をクリックしたときに、modalWindow[i]にactive2というクラスが付与されるようにして、その後一定時間経過後(今回は0.3秒に設定)に、modalWindow[i]についていたactive、active2のクラスが削除されるようにしている。
この部分の記述も一緒の部分が多いので、変数などを定義して短くすることもできるが、今回は分けて記載している。
以上で実装完了だ。
トラックバック & ピングバック
[…] 参考記事はこちら […]
[…] 前回の記事参照)。 […]
コメントはクローズされています。