背景
モーダルを作るってダルくないですか?
- モーダル自身を作成して・・・
- モーダルの背景を作成して・・・
- モーダル内のボタンを押すとモーダルと背景が非表示になって・・・
- モーダルの位置を固定して・・・
- 背景とz-index調整して・・・
- etc・・・
実装しないといけない要素が複数あるので面倒という話です。
そこで、この dialogタグ
の登場です。
簡単にいうと上記で列挙した機能を簡単に作ってくれるすごいやつです。
実は結構前(2014年)から存在していたのですが、対応しているブラウザがChrome
やOpera
のみでした。それが、2022年に 標準ブラウザで対応できるようになり、実用性が出てきたので、これを機に触って理解しようと思います。
WEB+DB vol.135 でも紹介されていますが、
実際に使ってみて「便利だな!」と思ったので、備忘録として残しました。
使い方
dialogが提供するメソッドと属性
- ダイアログを表示するための
show()
メソッド - モーダルダイアログを表示するための
showModal()
メソッド - ダイアログを閉じるための
close()
メソッド - ダイアログの状態を表す
open
属性
dialog/dialogModalを表示する
// dialog を取得
const dialog = document.getElementById('dialog');
// ダイアログを表示
dialog.show()
// モーダルダイアログを表示
dialog.showModal()
正直showDialog()
メソッドが一番期待できるメソッドでした。
モーダルの背景とその擬似要素をカスタムできるので、とても便利
dialog/dialogModalを閉じる
// dialog を取得
const dialog = document.getElementById('dialog');
dialog.show()
// モーダルダイアログを閉じる
dialog.close()
ダイアログが開いている状態
ダイアログが表示されていると、open
属性がダイアログタグに付与されます。
検証ツールでopen
を消すとモーダルを非表示にすることはできますが、その後showModal()
メソッドが効かず、モーダル再表示ができないなどのバグの種になるので直接操作するのは避けたほうがよさそうですね。
用意されているメソッドを素直に使っていきましょう。
では実際に書いてみます。
コード内容
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.dialog::backdrop {
backdrop-filter: blur(8px);
}
.dialog {
box-shadow: 0px 20px 36px 0px rgba(0, 0, 0, 0.6);
}
</style>
</head>
<body>
<button id="openButton">モーダルを開く</button>
<dialog id="modalDialog" class="dialog">
<div id="dialog-container">
<header>
<span>Header</span>
<button id="closeButton" type="button">
<p>閉じる</p>
</button>
</header>
<div>Message</div>
<form method="dialog">
<button type="submit" value="OK">Ok</button>
<button type="submit" value="CANCEL">Cancel</button>
</form>
</div>
</dialog>
<script>
const openButton = document.getElementById('openButton');
const modalDialog = document.getElementById('modalDialog');
// モーダルを開く
openButton?.addEventListener('click', async () => {
modalDialog.showModal();
// モーダルダイアログを表示する際に背景部分がスクロールしないようにする
document.documentElement.style.overflow = "hidden";
});
const closeButton = document.getElementById('closeButton');
// モーダルを閉じる
closeButton?.addEventListener('click', () => {
modalDialog.close();
// モーダルを解除すると、スクロール可能になる
document.documentElement.removeAttribute("style");
});
</script>
</body>
</html>
デモ
See the Pen Untitled by 小林誠 (@knicisow-the-vuer) on CodePen.
これでモーダルを表示/非表示を簡単に実装できました。
補足
スクロールの固定
document.documentElement.style.overflow = "hidden";
モーダルが表示されていても、背景部分がスクロールして鬱陶しいので、
モーダル表示時にスクロールを固定するギミックを追加しました。
::backdropによる擬似要素
ダイアログモーダル表示時に、擬似要素として表示される::backdrop
に対し、
ぼかし要素(blau)を追加することで背景も簡単にカスタムできます。
モーダルが閉じる際に引数に値を渡す
モーダルが閉じた時(closeイベントが発火した時)に、引数に値を渡すことができます。
検証のために以下を追記
// モーダルイベントが閉じる時の処理
modalDialog.addEventListener("close",() => {
alert(modalDialog.returnValue);
});
method="dialog"
submit
としてOk
とCancel
を用意しました。
form
要素でmethod="dialog"
と指定するとsubmit
でリクエストを送信せずに
dialog
を閉じることができます。
また、そのvalue
がdialog
のreturnValue
プロパティに格納されます。
よって、上記のコードを足した場合、モーダル上で
「Ok」をクリックすると「OK」とアラートが、
「Cancel」をクリックすると「CANCEL」とアラートが表示されます。
これによって、どのボタンをクリックしたのかの判別も容易です。
close()メソッド
モーダルを閉じるメソッドであるclose()
には引数に値を渡すことで、
その値をdialog
のreturnValue
プロパティに渡すことができます。
closeButton?.addEventListener('click', () => {
// 引数を渡す
modalDialog.close('閉じたで');
});
上記をさらに追記することで
「閉じる」をクリックすると「閉じたで」とアラートが表示されます。
モーダルを開く時にアニメーション
こっちはイージィ
以下のようにアニメーションをCSSで追加するだけ
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.dialog[open]{
animation-name: fadeIn;
animation-fill-mode: forwards;
animation-duration: 200ms;
animation-timing-function: ease-out;
}
モーダルを閉じる時にアニメーション
こっちが少々面倒くさい
というのも、close()
メソッドが発火するとopen
属性解除に伴い
diaplay:none;
スタイルが適用され、モーダルが非表示になるからです。
つまり、アニメーションが見える前に非表示になってしまう。
そのため、モーダル非表示時にアニメーションをつけるには
JS
で diaplay:none;
を制御してモーダルを出しわけする必要があります。
まず、
・open属性適用時のデフォルトスタイルを初期に適用
・非表示時のアニメーションを追加
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.dialog {
/* デフォルトスタイル */
display: block;
position: fixed;
inset-inline: 0;
inset-block: 0;
/* 非表示のアニメーション登録 */
animation-name: fadeOut;
animation-fill-mode: forwards;
animation-duration: 200ms;
animation-timing-function: ease-out;
}
モーダルに非表示スタイルを適用
<dialog id="modalDialog" class="dialog" style="display: none;">
モーダルを開く際に非表示スタイルを解除する
openButton?.addEventListener('click', async () => {
modalDialog.removeAttribute("style")
モーダルを閉じる(closeイベント発火)時に非表示スタイルを適用する
この時、アニメーションが見えるように、アニメーションを待機してから、
非表示スタイルを適用する。
modalDialog.addEventListener("close", async(e) => {
// アニメーション終了後に、スタイルを適用する
await waitDialogAnimation(e.target)
modalDialog.style.display = "none"
})
// アニメーションが完了するまで待機する
const waitDialogAnimation = (dialog) => Promise.allSettled(
Array.from(dialog.getAnimations()).map(animation => animation.finished)
);
デモ
See the Pen Untitled by 小林誠 (@knicisow-the-vuer) on CodePen.
モーダルを閉じる時にアニメーションをつけるUIはあまり見ないので、
もしかしたら需要はそんなにないかもしれない。
モーダルの背景をクリックして、モーダルを閉じたい
デフォルトではモーダルの背景をクリックしても、イベントは何も発火しません。
「背景クリック」でモーダルを閉じるようにしたければ、修正が必要です。
下記のようにdialog
の内部にもう1階層が必要
<dialog id="modalDialog" class="dialog">
<div id="dialogInputArea"> // ここが必要
// 略
</div>
</dialog>
背景にクリックイベントを追加
ダイアログモーダルが表示されている時、
ダイアログ要素(背景)をクリックするとモーダルを閉じるようにします。
//ダイアログのクリックイベント
modalDialog.addEventListener('click', (event) => {
if(event.target.closest('#dialogInputArea') === null) {
modalDialog.close();
}
});
背景クリックでモーダル非表示はUIとしても結構お目にかかることが多いので、
これはデフォルトで用意してほしかったなぁ。
感想
dialog
を使うことで、自作するよりも簡単に実装できますね。
というか「モーダルを実装するなら全部、dialog
でいいんじゃね?」
と思うくらいに便利だと思います。
safari
とかfirefox
は2022年3月と比較的最近対応したので
場合によっては動かない可能性があるので注意。
最後に
ここまで読んでくださりありがとうございます。
皆さんもぜひ dialog 使ってみてください!