🪟

dialogを使ったモーダルの作り方

2024/07/02に公開


dialogを使うと、シンプルなJavaScriptでモーダル(ダイアログ)が実装できます。
今回は、以下の要件を満たすモーダルを作成します!

  • 閉じるボタンを押したら、モーダルが閉じる
  • エスケープキーを押してもモーダルを閉じられる
  • モーダル外を押すとモーダルを閉じられる
  • モーダルが開いている間は、背景をスクロールさせない
  • モーダルが閉じられたら、背景をスクロールできるようにする
  • モーダルが複数あったら、開くボタンを押したモーダルに応じてモーダルを開く

HTMLでモーダルを作る

今回はモーダルがページ内に複数あっても正しく動くように作りたいので、2個dialogを用意します。
ポイントは開くボタンに設定されたdata-dialog="#js-dialog-1"と、開かれた後のdialogに設定されているdialog id="js-dialog-1"です。
末尾の数字を変えて複数のモーダルに対応するようにしています。

    <!-- ボタン -->
    <div class="dialog__button">
      <div class="dialog__button--inner">
        <p>以下のボタンをクリックすると、モーダル(ダイアログが開きます)</p>
        <button class="js-dialog-open c-button" data-dialog="#js-dialog-1">
          開く
        </button>
      </div>
    </div>
    <!-- ボタン -->
    <!-- ダイアログ -->
    <dialog
      id="js-dialog-1"
      class="dialog__open--inner"
      aria-label="ダイアログ"
    >
      <div class="dialog_inner">
        <p>中身です</p>
        <p>
          閉じるボタン、枠外、<br />エスケープキーを押すと<br />モーダルが閉じます
        </p>
        <p>テキストテキスト</p>
        <button class="js-dialog-close c-button" value="close">閉じる</button>
      </div>
    </dialog>

    <!-- ダイアログ -->
    <!-- ボタン -->
    <div class="dialog__button">
      <div class="dialog__button--inner">
        <p>2つ目のモーダルです</p>
        <button class="js-dialog-open c-button" data-dialog="#js-dialog-2">
          開く
        </button>
      </div>
    </div>
    <!-- ボタン -->
    <!-- ダイアログ -->
    <dialog
      id="js-dialog-2"
      class="dialog__open--inner"
      aria-label="ダイアログ"
    >
      <div class="dialog_inner">
        <p>2つ目のモーダルの中身です</p>
        <p>
          閉じるボタン、枠外、<br />エスケープキーを押すと<br />モーダルが閉じます
        </p>
        <p>テキストテキスト</p>
        <button class="js-dialog-close c-button" value="close">閉じる</button>
      </div>
    </dialog>
    <!-- ダイアログ -->

CSSで装飾

動きをわかりやすくするために、CSSで装飾をします。必須ではないので、CSSを書かなくてもモーダルの挙動は確認できます。

.dialog__button--inner {
  text-align: center;
  background-color: #f5f5f5;
  padding: 30px;
  margin: 30px auto;
}

.c-button {
  padding: 1em 2em;
  width: 300px;
  border-radius: 100px;
  font-weight: bold;
  text-align: center;
  display: inline-block;
}
.js-dialog-open {
  background-color: skyblue;
  margin-top: 30px;
}
.js-dialog-close {
  background-color: pink;
}

.dialog_inner {
  text-align: center;
}

JavaScriptで動きを制御

先に結論のコードです。解説はこの下から行なっています。

// body
const body = document.body;
// ダイアログ要素
const modal = document.querySelector("dialog");
// ダイアログを開くボタン(水色)
const dialogOpenButtons = document.querySelectorAll(".js-dialog-open");
// ダイアログを閉じるボタン(ピンク)
const dialogCloseButtons = document.querySelectorAll(".js-dialog-close");
// 水色のボタンが押されたら、ダイアログを開く。背景をスクロールさせない
dialogOpenButtons.forEach((button) => {
  const dialog = document.querySelector(button.dataset.dialog);
  button.addEventListener("click", () => {
    dialog.showModal();
    body.style.overflow = "hidden";
  });
});
// 閉じるボタンでダイアログを閉じる、背景をスクロールさせられるようにする
dialogCloseButtons.forEach((button) => {
  const dialog = button.closest("dialog");
  button.addEventListener("click", () => {
    dialog.close();
    body.style.overflow = "visible";
  });
});
// モーダルの外の黒い部分を押されたらダイアログを閉じる、背景をスクロールさせられるようにする
const modals = document.querySelectorAll(".dialog__open--inner");
modals.forEach((modal) => {
  modal.addEventListener("click", (event) => {
    if (!event.target.closest(".dialog_inner")) {
      modal.close();
      body.style.overflow = "visible";
    }
  });
});

// // エスケープキーを押されたら背景をスクロールさせられるようにする
modals.forEach((modal) => {
  modal.addEventListener("keydown", function (e) {
    if (e.key === "Escape") {
      body.style.overflow = "visible";
    }
  });
});

constで変数を作る

モーダルを制御するために、以下の部分をconstで変数に入れておきます。

  • body
  • dialog
  • ダイアログを開くボタン(水色)
  • ダイアログを閉じるボタン(ピンク)
// body
const body = document.body;
// ダイアログ要素
const modal = document.querySelector("dialog");
// ダイアログを開くボタン(水色)
const dialogOpenButtons = document.querySelectorAll(".js-dialog-open");
// ダイアログを閉じるボタン(ピンク)
const dialogCloseButtons = document.querySelectorAll(".js-dialog-close");

今回は複数のモーダルに対応したいので、取得したアイテムを配列で返してくれるquerySelectorAllを使っています。
ここがquerySelectorだと、HTML要素を探したときに最初に見つけたものを単一のデータで返します。

このあとの記述でforEachを使ってモーダルを制御したいのでquerySelectorAllで配列としてデータを取得できると都合が良いためquerySelectorAllを用いました。

開くボタンが押されたら

開くボタンが押されたら、押されたモーダルに該当するものが開くようにしたいです。
forEachでどこの開くボタンが押されたかを探します。

そのあと、押されたボタンに該当するdialogconstで変数としてもたせます。

これで「開くボタンに紐づくダイアログ」という情報を持っている変数dialogが作成できました。
body.style.overflow = "hidden";は、bodyにCSSを追記する記述です。
ボタンを押されたら(イコールモーダルが開いたら)背景を固定してスクロールをさせないようにします。

// 水色のボタンが押されたら、ダイアログを開く。背景をスクロールさせない
dialogOpenButtons.forEach((button) => {
  const dialog = document.querySelector(button.dataset.dialog);
  button.addEventListener("click", () => {
    dialog.showModal();
    body.style.overflow = "hidden";
  });
});

閉じるボタンが押されたら

行なっている処理は、開くボタンが押されたら…とあまり変わりません。

// 閉じるボタンでダイアログを閉じる、背景をスクロールさせられるようにする
dialogCloseButtons.forEach((button) => {
  const dialog = button.closest("dialog");
  button.addEventListener("click", () => {
    dialog.close();
    body.style.overflow = "visible";
  });
});

モーダル外をクリックしたらモーダルを閉じる

黒い部分がクリックされたら、の逆を考えます。モーダル内の白い部分を押された時はモーダルを閉じたくないので先にそこを変数でconst modals = document.querySelectorAll(".dialog__open--inner");として格納しましょう。

もしモーダルの白い部分をクリックされたら…をmodal.addEventListener("click"から記述します。

// モーダルの外の黒い部分を押されたらダイアログを閉じる、背景をスクロールさせられるようにする
const modals = document.querySelectorAll(".dialog__open--inner");
modals.forEach((modal) => {
  modal.addEventListener("click", (event) => {
    if (!event.target.closest(".dialog_inner")) {
      modal.close();
      body.style.overflow = "visible";
    }
  });
});

エスケープキーを押されたら

キー押下をkeydownaddEventListenerして、もしエスケープキーが押されたら背景のスクロール固定を解除します。

// // エスケープキーを押されたら背景をスクロールさせられるようにする
modals.forEach((modal) => {
  modal.addEventListener("keydown", function (e) {
    if (e.key === "Escape") {
      body.style.overflow = "visible";
    }
  });
});

完成

以下の要件を満たすモーダルが作成できました。

  • 閉じるボタンを押したら、モーダルが閉じる
  • エスケープキーを押してもモーダルを閉じられる
  • モーダル外を押すとモーダルを閉じられる
  • モーダルが開いている間は、背景をスクロールさせない
  • モーダルが閉じられたら、背景をスクロールできるようにする
  • モーダルが複数あったら、開くボタンを押したモーダルに応じてモーダルを開く

ページ内に複数モーダルがあるときに活用してください。

Discussion