こんにちは、虎の穴ラボの後藤です。
モーダルダイアログはユーザーに重要な情報を提供したり、決定を求めたりする際に適した UI です。
重要な UI である一方で、フォーカス制御やキーボード操作、スタイリング、背景要素の非活性など考慮すべき点は多く実装も複雑になりがちです。
そこでこの記事では、HTMLネイティブのdialog
要素を用いたシンプルなモーダルダイアログ実装を紹介します。
また、実際に試す中で気づいた実装のポイントについても紹介します。
dialog
要素の基本
モーダルを開く/閉じる
showModal()
メソッド: モーダルダイアログとして開きますclose()
メソッド: ダイアログを閉じます
<!DOCTYPE html> <html lang="ja"> <body> <div id="app"> <button id="showModalBtn" type="button">Show Modal</button> <dialog id="sampleModal"> <h1>HELLO MODAL !!</h1> <button id="closeModalBtn" type="button">Close Modal</button> </dialog> </div> <script> const showModalBtn = document.getElementById("showModalBtn"); // 表示ボタン const closeModalBtn = document.getElementById("closeModalBtn"); // 閉じるボタン // モーダル本体 const sampleModal = document.getElementById("sampleModal"); // 表示ボタンをクリック時したとき、 showModalBtn.addEventListener("click", () => { // モーダルを開く sampleModal.showModal(); }); // 閉じるボタンをクリック時したとき、 closeModalBtn.addEventListener("click", () => { // モーダルを閉じる sampleModal.close(); }); </script> </body> </html>
dialog
要素のメリット
モーダルダイアログの実装にHTMLネイティブのdialog
要素を利用すると下記のようなメリットがあります。
- 開く/閉じるをシンプルに実装できる
z-index
なしで最前面に表示される- 背景要素のスタイリングは
::backdrop
擬似要素を用いて設定できる
::backdrop - CSS: カスケーディングスタイルシート | MDN
- アクセシビリティの設定はブラウザが提供する
<dialog>: ダイアログ要素 - HTML: ハイパーテキストマークアップ言語 | MDN
ブラウザのサポート状況
記事執筆時点(2024/3/14)では、主要なブラウザの最新版であれば利用可能です。
Dialog element | Can I use... Support tables for HTML5, CSS3, etc
dialog 要素 + React の実装
基本的には、特に難しい点もなくdialog
要素に紐づくref
を用意し同様の操作をするだけです!
import { useRef } from "react"; export const DialogSample = () => { const dialogRef = useRef<HTMLDialogElement>(null); const handleShowModal = () => dialogRef.current?.showModal(); const handleCloseModal = () => dialogRef.current?.close(); return ( <> <button type="button" onClick={handleShowModal}> Show Modal </button> <dialog ref={dialogRef}> <h1>HELLO MODAL !!</h1> <button type="button" onClick={handleCloseModal}> Close Modal </button> </dialog> </> ); };
実装のポイント
1. dialog
要素は閉じても DOM に存在し続ける
はじめに、dialog
要素の表示状態はopen
属性で管理されます。
showModal()
メソッドによって表示するとdialog
要素にopen
属性が付与され、close()
メソッドによって閉じるとopen
属性は削除されます。
そのため、モーダルダイアログを開いても閉じてもopen
属性が変わるだけで、dialog
要素は DOM に残ったままです。
React における実装の注意点の一つとしては、close()
メソッドを呼び出した際にアンマウントが行われないことが挙げられます。
したがって「閉じた際に処理を行う」といった場合はアンマウントではなく、dialog
要素のclose
イベントで扱うと良さそうです。
HTMLDialogElement: close イベント - Web API | MDN
useEffect(() => { const dialog = dialogRef.current; if (!dialog) return; const handleCancel = () => { console.log("Dialog close event"); // 「閉じた際に背景要素のスクロールを許可する」といった処理はここで行う }; dialog.addEventListener("close", handleCancel); return () => { dialog.removeEventListener("close", handleCancel); }; }, []);
※ 補足ですがopen
属性は手動で削除すると予期せぬ動作になる可能性が高いので、読み取りのみの使用が推奨されます
https://html.spec.whatwg.org/multipage/interactive-elements.html#attr-dialog-open
2. ESC キーでモーダルダイアログを閉じる動作が提供される
アクセシビリティの観点では、モーダルダイアログは ESC キーで閉じることが一般的です。
showModal()
メソッドを使用すると、この動作はブラウザーが提供してくれます。
注意点としては、「閉じる際に必ず行うべき処理」があった際に、この動作を知っておかないと実装が漏れてしまう点です。
対応としては、ESC キーで閉じる際もclose
イベントが発火するので、こちらもclose
イベントで扱うと良さそうです。
3. showModal()
メソッドで開くとdialog
要素は Top layer に配置される
Top Layer に配置された要素は他のどのコンテンツよりも最前面に表示されます。
dialog
要素はshowModal()
メソッドで開くと Top Layer に配置され、これにより最前面に表示されるようになります。 ※ z-index
によるレイヤー調整は不要になります
実装の注意点としては、Top layer に配置されても DOM 構造は変わらない点です。
例えば次のようなスタイル指定があった時、DOM 構造は変わらないのでdialog
要素内のh1
の色も変わってしまいます。
// スタイリング #container h1 { color: aquamarine; } // React コンポーネント function App() { return ( <div id="container"> <dialog> <h1>HELLO MODAL !!</h1> </dialog> </div> ); }
この状況が望ましくないものの、他処理の関係でコンポーネントの記述を変えられない場合、React ではcreatePortal
を使うことで DOM 内の別部分へレンダーすることが可能です。
return ( <> <button type="button" onClick={handleShowModal}> Show Modal </button> {/* body 直下にレンダリングする */} {createPortal( <dialog ref={dialogRef}> <h1>HELLO MODAL !!</h1> <button type="button" onClick={handleCloseModal}> Close Modal </button> </dialog>, document.body )} </> );
まとめ
dialog
要素を利用するとシンプルにモーダルダイアログを実装できます。また主要な動作をブラウザが提供してくれる点も大きなメリットです。モーダルダイアログ実装の際は検討してみてください!
また、実装のポイントとして紹介しきれなかったものとして、多くの場面で必要になる「背景要素のスクロール制御」があります。下記の記事で詳しく紹介されていますので参照ください。
Fantia開発採用情報
虎の穴ラボでは現在、一緒にFantiaを開発していく仲間を積極募集中です!
多くのユーザーに使っていただけるtoCサービスの開発をやってみたい方は、ぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp