dialogを使ったモーダルの作り方
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
でどこの開くボタンが押されたかを探します。
そのあと、押されたボタンに該当するdialog
をconst
で変数としてもたせます。
これで「開くボタンに紐づくダイアログ」という情報を持っている変数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";
}
});
});
エスケープキーを押されたら
キー押下をkeydown
でaddEventListener
して、もしエスケープキーが押されたら背景のスクロール固定を解除します。
// // エスケープキーを押されたら背景をスクロールさせられるようにする
modals.forEach((modal) => {
modal.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
body.style.overflow = "visible";
}
});
});
完成
以下の要件を満たすモーダルが作成できました。
- 閉じるボタンを押したら、モーダルが閉じる
- エスケープキーを押してもモーダルを閉じられる
- モーダル外を押すとモーダルを閉じられる
- モーダルが開いている間は、背景をスクロールさせない
- モーダルが閉じられたら、背景をスクロールできるようにする
- モーダルが複数あったら、開くボタンを押したモーダルに応じてモーダルを開く
ページ内に複数モーダルがあるときに活用してください。
Discussion