素のJS!複数のモーダルウィンドウをHTMLのみの変更で複製する方法

素のJavaScriptでモーダルウィンドを設置し、HTMLのみの変更でモーダルウィンドを複製できるように素のJavaScriptで設置していきたいと思います。

前の記事でモーダルウィンドウの設置を書いたのですが、問題点としてモーダルウィンドを複数に増やしていくときにHTMLとJSを複製しないといけないコードだったので、今回は改善して、HTMLのみの変更でモーダルウィンドウを複製できるように改善いたしました!

HTMLのみの変更でモーダルウィンドウが複製できるようしたいんだけど、どうすればいいかな??

今回はこのお悩みを解決いたします!

僕の経歴は過去に企業常駐でコーダーとして経験を積み、現在はフリーランスコーダーとしてWEB業界4年目を迎えて、年間50サイトほど手掛けていてディレクション兼コーダーとして活動しており、JSが好きなのもあってJSのみの案件も受けたりもしております。

HTMLの変更のみでモーダルウィンドウを複製できるJSを書こう

まずは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 class="btnBox">
    <li class="btnBox__item"><a href="" class="" data-trigger="btn" data-modal="001">モーダル001</a></li>
    <li class="btnBox__item"><a href="" class="" data-trigger="btn" data-modal="002">モーダル002</a></li>
    <li class="btnBox__item"><a href="" class="" data-trigger="btn" data-modal="003">モーダル003</a></li>
  </ul>

  <!-- modal start -->
  <div class="modal" data-modal="box">
    <div class="modal__bg" data-modal="bg"></div>
    <div class="modal__inner" data-modal="inner">

      <!-- modal001 start -->
      <div class="modal-card" data-trigger="item" data-modal="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-trigger="item" data-modal="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-trigger="item" data-modal="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>

お次は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);
}

そしてJSコードはこちら!

'use strict';

const modalBtns = document.querySelectorAll('[data-trigger="btn"]');
const modalItems = document.querySelectorAll('[data-trigger="item"]');
const modalCloseBtns = document.querySelectorAll('[data-modal="close"]');
const regex = /[^0-9]/g;

/* modal close */
modalCloseBtns.forEach(modalCloseBtn => {
  modalCloseBtn.addEventListener('click', function(e) {
    e.currentTarget.closest('[data-modal="box"]').querySelector('[data-modal="bg"]').classList.remove('is-active');
    e.currentTarget.closest('[data-modal="box"]').querySelector('[data-modal="inner"]').classList.remove('is-active');
    const cardModals = e.currentTarget.closest('[data-modal="inner"]').querySelectorAll('[data-trigger="item"]');
    cardModals.forEach(cardModal => {
      cardModal.classList.remove('is-active');
    });
  });
});

/* modal open */
modalBtns.forEach(modalBtn => {
  modalBtn.addEventListener('click', function(e) {
    e.preventDefault();
    const modalBtnNum = e.currentTarget.dataset.modal;

    modalItems.forEach(modalItem => {
    const modalItemNum = modalItem.dataset.modal.replace(regex, "");
      if (modalItemNum == modalBtnNum) {
        document.querySelector('[data-modal="bg"]').classList.add('is-active');
        document.querySelector('[data-modal="inner"]').classList.add('is-active');
        modalItem.classList.add('is-active');
      }
    });
  });
});

コードの内容を説明します

まず前回の記事で変えた変更は、HTMLのdata属性になります。

このように1つのHTML要素に2つのデータ属性をボタン部分と、モーダルパーツ部分に設置しております。「data-trigger」というdata属性を追加したのですが、こちらはJSで要素を取得するためだけのdata属性となっております。

data-triggerで要素をすべて取得したのですが、「data-modal」のdata属性の方はどのボタンを押した時にどのモーダルを表示するか?という照合するためのdata属性になります。JSの動き的にはクリックしたボタンの番号を覚えておき、その番号と同じ番号を持つモーダルを表示するという動きになってきます。

CSSは前回からまったく触ってないので、次はJSの動きを再確認していきましょう。

やってることは単純で、要はボタンのdata属性の数字とモーダルのdata属性の数字を取得してそれを照合し、数字が合ったモーダルのみ表示という動きをさせております。よくあるforEachですべて取得して、その中のif文で取得要素を絞るパターンですね。

データ属性に入れた数字を取得するには「dataset.modal」と書くことで取得できて、モーダルの方はdata属性の値が「modal001」というふうに数字だけではないので、「正規表現」という機能で使い(呪文みたいなやつ)、数字以外を抽出して、replace()という機能で数字以外のすべてを削除する処理をして、数字のみにしております。

今回もう1つこだわった箇所が、下記部分です。

addEventListenerでクリックイベントをモーダルクローズボタンに設置しているのですが、そのfunction()に「function(e)」とカッコの中に「e」入れることでクリックされた要素の情報を取得できます。それをe.currentTargetと書くことでクリックされた要素を取得できるので、e.currentTargetを起点としてそれぞれの要素を取得しクラスは外す処理をさせています。

処理の範囲をaddEventListener内に絞れるので、新たにHTMLすべてを検索する(document.querySelectorなど)場合と比べて処理が速くなっていると思います。(気持ち程度か?)変数に一度データを入れて、そこから処理をするというのも遅いと聞いたことがあるので、もしかしたら処理がコンマ単位で速くなってるかもですね。まぁ可読性を損なわない程度にやった方がいいかもですが、、。

これまでのコードをダウンロードできるようにしておきましたので下記ボタンからダウンロードして直接コードを確認できます。

もし今回わからない箇所などありましたら、下記からご相談いただけましたらありがたいです。

モーダルウィンドウはJQueryのライブラリよりも素のJSがおすすめ

JQueryで簡単にモーダルウィンドウを設置できるんですが、やはりライブラリだと使わない機能がいっぱい含まれていたり、逆に機能の範囲でしかカスタマイズが難しかったりして融通が効かないことがあるので、モーダルウィンドウぐらいなら素のJavaScriptで簡単に書くのがシンプルで動きも早くて、そしてカスタマイズが自由自在になるので、いいかと思います。

使わない機能が多いということはその分JSコードが多いということになるので、パフォーマンスの低下は魔逃れないのと、JQueryのライブラリ自体もコード量が多いので、パフォーマンスは微妙にも落ちてくるかと思います。なので、要はチリツモで遅くなるのがページスピードなので、JQueryのライブラリの採用はちゃんと検討してからの方がいいかもです。

もちろん開発スピード優先なら、ぜんぜんJQueryのライブラリで目的に合致するので否定しているわけではないですのでご了承ください。

この記事を書いた人

アバター

トノムラマサシ

masashi
Webサイト屋兼ブロガー|過去に企業常駐などを経て現在はフリーランスでディレクションとコーダーとして活動しております。JS大好きなのでJSの仕事依頼お待ちしております。京都在中で5人家族。