【HTML】徹底解説 dialog 要素 – マークアップからスタイリング、JavaScript による操作まで

[2022-07-28 更新]

dialog 要素はかなり以前より HTML5 仕様に存在していましたが、Safari がサポートしていなかったことでなかなか現場で使われませんでした。しかし、2022 年 3 月の Safari 15.4dialog 要素が実装されたことで、ようやく現場でも使える要素になりました。

今回は 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-colorrgba(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 要素のお世話になりそうな気がします。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share