[2022-07-28 更新]
dialog
要素はかなり以前より HTML5 仕様に存在していましたが、Safari がサポートしていなかったことでなかなか現場で使われませんでした。しかし、2022 年 3 月の Safari 15.4 に dialog
要素が実装されたことで、ようやく現場でも使える要素になりました。
今回は dialog
要素のマークアップにとどまらず、CSS によるスタイリング、そして、JavaScript によるダイアログの操作まで徹底解説します。
dialog 要素とは
dialog
要素はダイアログボックスやモーダルウィンドウと呼ばれるユーザーインタフェースを担います。これまで Bootstrap といった CSS や JS ライブラリーがこういったウィンドウをサポートしてきましたが、dialog
要素の登場によってライブラリーに依存せずにダイアログボックスやモーダルウィンドウといったユーザーインタフェースを実現できるようになりました。
標準化では曰つきの dialog 要素
実は W3C にて HTML5 仕様の初期草案の段階から dialog
要素は存在していました。2008 年当時の草案を見ると、そのときはなんと会話をマークアップするための要素でした。その後、草案もバージョンアップしていく過程で 2010 年に dialog
要素は草案から削除されました。
2012 年に現在の dialog 要素として復活しました。しかし、2014 年の草案で削除され、そのまま HTML5 仕様は勧告になりました。
その後、2017 年に勧告となった HTML 5.2 で復活し、現在は WHATWG HTML 仕様で Living Standard として存続しています。
このように dialog
要素の標準化作業は長年難航し、復活しては消えるという繰り返しでした。私自身、ウェブ開発者として dialog
要素の登場を待ち遠しく感じていたこともあり、Safari でのサポートのニュースを知った時には心躍りました。
dialog 要素のマークアップ
前置きが長くなりましたが、早速 dialog
要素のマークアップから見ていきましょう。
<dialog open>
<h1>コマンド実行</h1>
<p>本当にコマンドを実行してもよろしいですか?</p>
<button class="cancel">キャンセル</button>
<button class="execute">実行</button>
</dialog>
このマークアップを既存のページに埋め込むと、次のようにレンダリングされるはずです(以下はキャプチャー画像です)。
デフォルトスタイルはそっけないのですが、他の HTML 要素と異なり、このコンテンツはページの所定の位置にオーバーレイした状態でレンダリングされます。すでにダイアログボックスらしく振舞っていますね。
dialog
要素には open
属性が用意されています。open
属性はダイアログを表示状態にするのか非表示にするのかを指示する Boolean 属性です。前述の例では open
属性をマークアップしていますので、ダイアログが表示された状態になります。
ダイアログ表示の制御
ダイアログである以上、前述のサンプルのように最初から表示された状態というのは考えにくいでしょう。そのため、JavaScript から表示と非表示を制御する必要があります。
次のサンプルでは、button
要素と dialog
要素がマークアップされています。dialog
要素には open
属性がマークアップされていませんので、ダイアログは表示されない状態です。
<button id="btn-1">実行</button>
<dialog id="dialog-1">
<h1>コマンド実行</h1>
<p>本当にコマンドを実行してもよろしいですか?</p>
<button class="cancel">キャンセル</button>
<button class="execute">実行</button>
</dialog>
// 実行ボタンが押されたときの処理
const dialog = document.getElementById('dialog-1');
document.getElementById('btn-1').addEventListener('click', (event) => {
dialog.show();
});
ダイアログを表示するには、dialog
要素の DOM オブジェクトの show()
メソッドを使います。引数も戻り値もありません。
このサンプルではボタンを押すとダイアログが表示されます。しかし、ひとたび表示されるとダイアログを非表示にすることができません。面倒ですが、このページを再読み込みして、ここまで戻ってきてください。では「実行」ボタンを押してこのサンプルを次の試してみてください。
ダイアログ非表示の制御
前述のサンプルではまだダイアログの中のボタンの制御を実装していなかったため、一度表示してしまったダイアログを非表示にすることができませんでした。ダイアログを非表示にするには、dialog
要素の DOM オブジェクトの close()
メソッドを呼び出します。
次のサンプルでは、ダイアログの中にあるボタンを押すと、ダイアログを非表示にして、画面にどのボタンを押したのかを表示します。
<button id="btn-2">実行</button>
<span id="res-2"></span>
<dialog id="dialog-2">
<h1>コマンド実行</h1>
<p>本当にコマンドを実行してもよろしいですか?</p>
<button class="cancel">キャンセル</button>
<button class="execute">実行</button>
</dialog>
// 実行ボタンが押されたときの処理
const dialog = document.getElementById('dialog-2');
document.getElementById('btn-2').addEventListener('click', () => {
dialog.show();
});
const res = document.getElementById('res-2');
// キャンセルボタンが押されたときの処理
dialog.querySelector('.cancel').addEventListener('click', () => {
res.textContent = 'キャンセル';
dialog.close();
});
// 実行ボタンが押されたときの処理
dialog.querySelector('.execute').addEventListener('click', () => {
res.textContent = '実行';
dialog.close();
});
次の「実行」ボタンを押してこのサンプルを次の試してみてください。
モーダルダイアログ
前述のサンプルでは、ダイアログが表示されている間、背景となるページのコンテンツを操作できてしまいます。テキストは選択できますし、もしフォームコントロールがあれば操作も可能です。多くのシーンでダイアログを表示している間は背景となるページのコンテンツを操作できないようにしたいのではないでしょうか。
もし背景コンテンツを操作できないようにするには、dialog
要素の DOM オブジェクトの showModal()
メソッドを使います。前述のサンプルの show()
メソッドを showModal()
メソッドに変更するだけです。
dialog.showModal();
次の「実行」ボタンを押してモーダルダイアログを試してください。
ダイアログを表示させると背景が薄暗くなり、モーダルであることが視覚的にも分かります。
open プロパティを操作してはダメなのか
前述のサンプルでは、dialog
要素のダイアログの表示と非表示に、DOM オブジェクトの show()
メソッド、showModal()
メソッド、そして、close()
メソッドを使いました。しかし dialog
要素には open
属性があるのだから、JavaScript から open
プロパティを操作する方法もあると気づいた方もいらっしゃるでしょう。もちろん、期待通りに動作します。前述のサンプルを open
プロパティの操作に置き換えてみましょう。
<button id="btn-4">実行</button>
<span id="res-4"></span>
<dialog id="dialog-4">
<h1>コマンド実行</h1>
<p>本当にコマンドを実行してもよろしいですか?</p>
<button class="cancel">キャンセル</button>
<button class="execute">実行</button>
</dialog>
// 実行ボタンが押されたときの処理
const dialog = document.getElementById('dialog-4');
document.getElementById('btn-4').addEventListener('click', () => {
dialog.open = true;
});
const res = document.getElementById('res-4');
// キャンセルボタンが押されたときの処理
dialog.querySelector('.cancel').addEventListener('click', () => {
res.textContent = 'キャンセル';
dialog.open = false;
});
// 実行ボタンが押されたときの処理
dialog.querySelector('.execute').addEventListener('click', () => {
res.textContent = '実行';
dialog.open = false;
});
次の「実行」ボタンを押すと、実際に試せます。
期待通りに動作したのではないでしょうか。ところが、open
プロパティを使ってダイアログを閉じることは非推奨になっています。理由は次の通りです。
close
イベントが発生しない- 一度
open
プロパティにfalse
をセットしてダイアログを閉じてしまうと、その後close()
メソッドでそのダイアログを閉じられなくなる showModal()
メソッドでダイアログを表示してからopen
プロパティにfalse
をセットしてダイアログを閉じると、ドキュメントは依然ブロックされたままとなる
この中でとりわけ 3 つ目の理由は致命的なのではないでしょか。実際に試してみましょう。次の「実行」ボタンを押してみてください。ただし、ダイアログを閉じてもドキュメントがブロックされたままになるはずですので、その際にはこのページを再読み込みしてください。
close イベントと returnValue プロパティ
dialog
要素の DOM オブジェクトの close()
メソッドでダイアログを閉じると、その dialog
要素の DOM オブジェクトで close
イベントが発生します。
close
イベントとともに良く使われるのが、close()
メソッドの第一引数と returnValue
プロパティです。close()
メソッドには文字列を引数に与えることができます。この close()
メソッドに引き渡された値は dialog
要素の DOM オブジェクトの returnValue
プロパティにセットされます。close
イベントのイベントリスナ―で returnValue
プロパティの値を読み取ります。
このような使い方をこれまでのサンプルに当てはめると次のようになります。
// キャンセルボタンが押されたときの処理
dialog.querySelector('.cancel').addEventListener('click', () => {
dialog.close('キャンセル');
});
// 実行ボタンが押されたときの処理
dialog.querySelector('.execute').addEventListener('click', () => {
dialog.close('実行');
});
// ダイアログが閉じられた時の処理
dialog.addEventListener('close', (event) => {
const res = document.getElementById('res-3');
res.textContent = event.target.returnValue;
});
ダイアログを閉じる処理と、閉じた後の処理をイベントでつなぐことで、コードを完全に分離し、コードの見通しを良くするのに役立てられるのではないでしょうか。
なお、returnValue
プロパティは close()
メソッドを通してではなく直接的に文字列をセットすることもできます。
cancel イベント
dialog
要素で表示されたダイアログをキャンセルすると dialog
要素の DOM オブジェクトで cancel
イベントが発生します。
ダイアログをキャンセルするには、ダイアログ表示中にキーボードの ESC キーを押します。ESC キーを押すとダイアログは非表示になりますが close
イベントも発生します。
dialog.addEventListener('cancel', () => {
console.log('ダイアログがキャンセルされました。');
});
dialog.addEventListener('close', () => {
console.log('cancel イベントと合わせて close イベントも発生します。');
});
もし ESC キーを押してもダイアログが閉じないようにしたいなら、cancel
イベントのデフォルトアクションを抑止します。
dialog.addEventListener('cancel', (event) => {
event.preventDefault();
});
バックドロップのスタイリング
showModal()
メソッドでモーダルのダイアログを表示すると背景が暗くなりますが、この背景をバックドロップと呼びます。Chrome の場合、デフォルトではバックドロップの background-color
に rgba(0, 0, 0, 0.1)
がセットされます。すでにご覧になった通り、これはうっすらと黒みがかったレイヤーがページ全体を覆う感じになります。
そのバックドロップを CSS でスタイリングするには、::backdrop
疑似要素を使います。次の例は半透明度を少し下げて黒みを強くしています。
dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
::backdrop
疑似要素には backdrop-filter
プロパティを使うこともできます。次の例では背景をぼかしています。2022 年 7 月現在、backdrop-filter
プロパティは Chrome、Edge、Firefox で動作します。Safari では -webkit-
プレフィックスが必要です。
dialog::backdrop {
backdrop-filter: blur(15px);
-webkit-backdrop-filter: blur(15px); /* Safari 用 */
}
次の「実行」ボタンを押すと、実際にぼかし効果を付けたダイアログが表示されます。もしボタンが押せなかったら、このページを再読みしてから試してください。
まとめ
モーダルダイアログはかなり以前よりニーズが高い機能がゆえに、さまざまな JS/CSS ライブラリーがそれをサポートしてきました。dialog
要素の誕生およびブラウザーのサポートによって、やっとライブラリーに頼らなくても良さそうです。
個人的な感想を言うと、dialog
要素は機能面では必要十分であり、思った以上に簡単に扱えそうなので、今後は dialog
要素のお世話になりそうな気がします。
今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。