dialog要素を使ってモーダルを実装する際、よく必要になる処理をいくつか実装してみます。
サンプルコード
まずはシンプルなモーダルを実装してみます。
<button data-dialog-link="dialog01">dialog01</button>
<dialog class="dialog" data-dialog-main="dialog01">
<div class="dialog_inner">
<div class="dialog_content">
<div class="dialog_scroll">
<p>dialogの内容</p>
</div>
</div>
<button class="dialog_close" data-dialog-close="dialog01">
<span class="dialog_close-txt visually-hidden">dialogを閉じる</span>
</button>
</div>
</dialog>
ボタンとモーダルの紐づけなどはdata属性を使って実装しています。
次にJavaScriptでモーダルの開閉処理を追加します。
const dataDialogMain = 'data-dialog-main';
const dataDialogLink = 'data-dialog-link';
const dataDialogClose = 'data-dialog-close';
const $dialogLinks = document.querySelectorAll(`[${dataDialogLink}]`);
const $dialogCloses = document.querySelectorAll(`[${dataDialogClose}]`);
// モーダルリンククリック時
$dialogLinks.forEach(function(element) {
element.addEventListener('click', function(e) {
e.preventDefault();
const dialogId = element.getAttribute(`${dataDialogLink}`);
const $dialog = document.querySelector(`[${dataDialogMain} = ${dialogId}]`);
if($dialog) $dialog.showModal();
});
});
// 閉じるボタンクリック時
$dialogCloses.forEach(function(element) {
element.addEventListener('click', function(e) {
e.preventDefault();
const dialogId = element.getAttribute(`${dataDialogClose}`);
const $dialog = document.querySelector(`[${dataDialogMain} = ${dialogId}]`);
if($dialog) $dialog.close();
});
});
最後にモーダルの基本的なスタイル設定を行います。
.dialog {
inset: unset;
max-width: unset;
max-height: unset;
overflow: unset;
border: none;
padding: 0;
background: none;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100dvh;
}
.dialog:not([open]) {
display: none !important;
}
.dialog::backdrop {
background: rgba(0, 0, 0, 0.7);
}
.dialog_inner {
max-width: 1200px;
position: relative;
width: 90%;
padding-top: 50px;
}
.dialog_content {
background: white;
}
.dialog_scroll {
height: 100%;
max-height: calc(90dvh - 50px);
overflow-y: auto;
padding: 20px;
}
.dialog_close {
appearance: none;
padding: 0;
border: none;
background: transparent;
display: block;
position: absolute;
top: -7px;
right: -7px;
width: 44px;
height: 44px;
cursor: pointer;
}
.dialog_close::before,
.dialog_close::after {
content: '';
display: block;
position: absolute;
top: calc(50% - 1px);
left: calc(50% - 15px);
width: 30px;
height: 2px;
background: white;
}
.dialog_close::before {
rotate: 45deg;
}
.dialog_close::after {
rotate: -45deg;
}
これで基本的なモーダルの実装ができました。
モーダルのデモページ
開閉時のアニメーション
次にモーダルの開閉時のアニメーション(フェード)を実装してみます。
以前に@starting-styleの記事でも試した、CSSの@starting-styleを使った方法になります。
.dialog {
inset: unset;
max-width: unset;
max-height: unset;
overflow: unset;
border: none;
padding: 0;
background: none;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100dvh;
}
.dialog:not([open]) {
display: none !important;
}
.dialog::backdrop {
background: rgba(0, 0, 0, 0.7);
}
.dialog, .dialog::backdrop {
opacity:0;
transition:
opacity 1000ms,
overlay 1000ms allow-discrete,
display 1000ms allow-discrete;
}
.dialog[open], .dialog[open]::backdrop {
opacity: 1;
@starting-style{
opacity: 0;
}
}
.dialog_inner {
max-width: 1200px;
position: relative;
width: 90%;
padding-top: 50px;
}
.dialog_content {
background: white;
}
.dialog_scroll {
height: 100%;
max-height: calc(90dvh - 50px);
overflow-y: auto;
padding: 20px;
}
.dialog_close {
appearance: none;
padding: 0;
border: none;
background: transparent;
display: block;
position: absolute;
top: -7px;
right: -7px;
width: 44px;
height: 44px;
cursor: pointer;
}
.dialog_close::before,
.dialog_close::after {
content: '';
display: block;
position: absolute;
top: calc(50% - 1px);
left: calc(50% - 15px);
width: 30px;
height: 2px;
background: white;
}
.dialog_close::before {
rotate: 45deg;
}
.dialog_close::after {
rotate: -45deg;
}
これでフェードでの開閉になりました。
モーダルの開閉アニメーションのデモページ
以前の記事でも補足していますが、記事作成時点でFirefoxでは閉じる際のアニメーション(フェード)が対応していません。
モーダル背景をクリックして閉じる
最後にモーダルの背景をクリックでモーダルを閉じる処理です。
モーダルのコンテンツ範囲(白背景の範囲)にclassを付与します。
<button data-dialog-link="dialog01">dialog01</button>
<dialog class="dialog" data-dialog-main="dialog01">
<div class="dialog_inner">
<div class="dialog_content js-modal-content">
<div class="dialog_scroll">
<p>dialogの内容</p>
</div>
</div>
<button class="dialog_close" data-dialog-close="dialog01">
<span class="dialog_close-txt visually-hidden">dialogを閉じる</span>
</button>
</div>
</dialog>
JavaScriptで背景クリックで閉じる処理を追加します。
const dataDialogMain = 'data-dialog-main';
const dataDialogLink = 'data-dialog-link';
const dataDialogClose = 'data-dialog-close';
const jsDialogContent = 'js-modal-content';
const $dialogs = document.querySelectorAll(`[${dataDialogMain}]`);
const $dialogLinks = document.querySelectorAll(`[${dataDialogLink}]`);
const $dialogCloses = document.querySelectorAll(`[${dataDialogClose}]`);
// モーダルリンククリック時
$dialogLinks.forEach(function(element) {
element.addEventListener('click', function(e) {
e.preventDefault();
const dialogId = element.getAttribute(`${dataDialogLink}`);
const $dialog = document.querySelector(`[${dataDialogMain} = ${dialogId}]`);
if($dialog) $dialog.showModal();
});
});
// ダイアログ背景クリック時
$dialogs.forEach(function(element) {
element.addEventListener('click', function(e) {
console.log(e.target);
// ダイアログのコンテンツ外(背景)をクリックしている場合
if (e.target.closest(`.${jsDialogContent}`) === null) {
const $dialog = e.currentTarget.closest(`[${dataDialogMain}]`);
if($dialog) $dialog.close();
}
});
});
// 閉じるボタンクリック時
$dialogCloses.forEach(function(element) {
element.addEventListener('click', function(e) {
e.preventDefault();
const dialogId = element.getAttribute(`${dataDialogClose}`);
const $dialog = document.querySelector(`[${dataDialogMain} = ${dialogId}]`);
if($dialog) $dialog.close();
});
});
dialog要素がクリックされた際に、祖先要素に.js-modal-contentがあるかどうかでコンテンツ内をクリックしたかコンテンツ外をクリックしたかを判別しています。
モーダルの背景をクリックで閉じるデモページ
コメントが承認されるまで時間がかかります。