モーダルの実装をmicromodalから<dialog>要素に置き換えることを考える
概要
Webサイト制作において、開いたら一時的にその要素のみが操作できる「モーダルダイアログ」を実装することがよくある。
これの実装をするのに、これまで micromodal
というライブラリを使って、<div>
要素を操作する形で実装していた。
しかし、最近のHTMLでは <dialog>
要素が仕様に追加されている。
このスクラップでは、 <dialog>
要素の実用性・実際に使用する際の使い方・注意点などを調べていきたい。
<dialog>
要素に置き換えることで得られそうなメリット
- セマンティックなタグで表すことで、divタグよりも役割を正しく表せるのではないか
- 標準仕様なので、実装・命名が統一できるのではないか(同じ操作でもライブラリによって、openだったりshowだったりすることがある)
- ブラウザ側で実装される機能については、実装を省略できるのではないか。
ちなみに、「そもそもWebサイトにモーダルが必要なのか」という議論もあり、実装の際にはこれはこれで検討すべきだとは思うのですが、本稿では必要性は置いておいて実装方法についての議論をします。
モーダルを実装する際に必要/ほしい要件の整理
機能
- ボタンをクリック(キー操作の場合はEnter)するとモーダルを開く
- モーダルを開いたとき、モーダル内にフォーカスが移る
- モーダルを開いている間は、Tab操作の範囲がモーダルの中だけになる(フォーカストラップ)
- モーダルを開いている間は、背面がスクロールできなくなる
- モーダルの背景に黒半透明のオーバーレイを設定できる
- モーダル内の閉じるボタンをクリックすると閉じる
- 背景オーバーレイをクリックすることでも閉じる
-
Esc
キーでも閉じる - モーダルを閉じたとき、モーダルが開く前にフォーカスされていた要素にフォーカスが移る
アニメーション
- 開閉時のアニメーションが設定できる
アクセシビリティ・WAI-ARIA
モーダルとして必要な要素がよく分かっていないので、これも含めて調べたい。
- モーダルを開いていないときは、モーダルが読み上げされない状態になっている(
aria-hidden="true"
など) - モーダルを開いているときは、モーダル以外が読み上げされない状態になっている
- モーダルに
role="dialog"
が設定されている - モーダルに
aria-modal="true"
が設定されている - ダイアログには適切なラベルがある(
aria-labelledby
など)。
よくわかっていないこと
-
role="dialog"
とaria-modal="true"
ってどっちも付ける必要あるん?(aria-modal="true"ってどういう意味?) -
role="dialog"
とrole="alertdialog"
の違いは?
micromodalで実装する場合
Micromodal.js - Tiny javascript library for creating accessible modal dialogs
<!-- [1] -->
<div id="modal-1" aria-hidden="true">
<!-- [2] -->
<div tabindex="-1" data-micromodal-close>
<!-- [3] -->
<div role="dialog" aria-modal="true" aria-labelledby="modal-1-title" >
<header>
<h2 id="modal-1-title">Modal Title</h2>
<!-- [4] -->
<button aria-label="Close modal" data-micromodal-close></button>
</header>
<div id="modal-1-content">Modal Content</div>
</div>
</div>
</div>
<button data-micromodal-trigger="modal-1">モーダルを開く</button>
import MicroModal from 'micromodal';
MicroModal.init();
micromodalの特徴
- data-micromodal-trigger のボタンをクリックすると、対応するidのついた要素(
[1]
)のaria-hidden
が変更される。- aria-hiddenの値に合わせてCSSを設定することで、表示非表示を切り替える。
- モーダルを開いたとき、モーダル内にフォーカスが移る。
- モーダルを開いている間は、Tab操作の範囲がモーダルの中だけになる
- モーダルを開いている間、背面がスクロールできなくする設定がある
-
disableScroll
オプション
-
-
[2]
の要素がオーバーレイになる- スタイルはCSSで設定
-
[2]
の要素(オーバーレイ)に data-micromodal-close をつけているので、クリックするとモーダルを閉じる - Escキーでも閉じることができる
- モーダルを閉じたとき、モーダルが開く前にフォーカスされていた要素にフォーカスが移る
- CSS animationプロパティで開閉時のアニメーションが設定できる
-
awaitOpenAnimation
,awaitCloseAnimation
オプションをtrueにすることで、animationが完了するまで待ってくれる。
-
- モーダルを開いていないときは、
aria-hidden="true"
で、読み上げされないことが明示される。 - (モーダル以外の読み上げ状態はよくわからず。)
- モーダルに role="dialog" が設定されている
- モーダルに aria-modal="true" が設定されている
- ダイアログにはaria-labelledbyでラベルがつけられている
最後の方のroleやaria属性は、サンプル通りに作る前提。
<dialog>
要素とは
HTML5.2で勧告され、現行のLiving Standardで標準となっているHTMLの標準仕様(MDNより)。
ダイアログボックスやアラートを表す。
open
属性
<dialog open>
が開いた状態のダイアログ、
<dialog>
は開いていないダイアログを表す。
イメージとしては、<input type="checkbox" checked>
的なイメージだろうか。
::backdrop
擬似要素
dialog要素がモーダルとして開いた際、 ::backdrop
疑似要素が表示される。
背景を薄いグレーでオーバーレイする用途で使われる。
ちなみに、::backdrop
擬似要素自体はdialog要素専用のものではなく、videoを全画面表示した際にも使われる。
操作
<dialog>
要素は、 HTMLDialogElement
インターフェースを持ち、ダイアログを開閉するためのメソッドを持つ。
-
show()
ダイアログをモードレス(他のコンテンツも操作できる状態)で開く -
showModal()
ダイアログをモーダル(ダイアログのみが操作できる状態)で開く -
close()
ダイアログを閉じる
ブラウザの実装状況
上記の説明はあくまで仕様の話で、実装されているかどうかは別の話。
Can I Use で調べてみる。
- Edge 79 (2020/1) ~
- Firefox 98 (2022/3) ~
- Chrome 37 (2014/8) ~
- Safari 15.4 (2022/3) ~
- Opera 24 (2014/9) ~
IEはもう終了するのでいいとして、
一応メジャーなブラウザでは一通り実装されたという状態にはなった。
とはいえ、FirefoxやSafariの実装はつい最近の話なので、ユーザーが使える状態になっているとは言えない。
ポリフィル
dialogが実装されていないブラウザのため、Googleがポリフィルを提供している。
dialog 要素のチェック
以下の2つで動作を見てみる
MDNのデモ
dialog-polyfillのデモ
-
ボタンをクリック(キー操作の場合はEnter)するとモーダルを開く
- →JS実装は必要。クリックイベントで
showModal()
(またはshow()
)を実行する。
- →JS実装は必要。クリックイベントで
-
モーダルを開いたとき、モーダル内にフォーカスが移る
- →ChromeでMDNのデモを確認したところ、フォーカス移ってるっぽい
-
モーダルを開いている間は、Tab操作の範囲がモーダルの中だけになる(フォーカストラップ)
- →
showModal()
で開かれたモーダルであっても、フォーカストラップはされない。 - polyfillのデモでフォーカストラップされているのは、追加で実装されているみたい。
- polyfillのデモでは、完全にフォーカストラップされているわけではなく、Chromeのアドレスバーにはフォーカス移動できる状態である。
- →
-
モーダルを開いている間は、背面がスクロールできなくなる
- →別途実装が必要
-
モーダルの背景に黒半透明のオーバーレイを設定できる
- →
::backdrop
疑似要素が使える
- →
-
モーダル内の閉じるボタンをクリックすると閉じる
- 閉じるボタンのクリックイベントで
close()
を実行する。
- 閉じるボタンのクリックイベントで
-
背景オーバーレイをクリックすることでも閉じる
- polyfillのデモでbackdropをクリックしたときに色を変えるというデモがあったので、そのクリックイベントでclose()すればできそう
-
Escキーでも閉じる
- MDNのデモで動作したので、できそう
-
モーダルを閉じたとき、モーダルが開く前にフォーカスされていた要素にフォーカスが移る
- なってそう
-
開閉時のアニメーションが設定できる
-
モーダルを開いていないときは、モーダルが読み上げされない状態になっている(aria-hidden="true" など)
- 実装されているブラウザでは、
dialog:not([open]) { display: none; }
のスタイルが当たるので読み上げからは除外されるはず
- 実装されているブラウザでは、
-
モーダルを開いているときは、モーダル以外が読み上げされない状態になっている
- (よくわかってない)
-
モーダルに role="dialog" が設定されている
-
<dialog>
要素の暗黙のロールが"dialog"
なのでOK。
-
-
モーダルに aria-modal="true" が設定されている
- 追加で指定必要?
-
ダイアログには適切なラベルがある(aria-labelledby など)。
- 指定が必要
結論
- dialog要素使う場合、dialog-polyfillを入れた上でフォーカストラップも実装(focus-trap - npm など?)する必要があり、それを考えるとmicromodalのようなライブラリを使うほうが良さそう
その他調べたこと
aria-modal
の動作
あるコンテナ要素に aria-modal="true" という属性が適用されている場合、その要素がモーダルであることをスクリーンリーダーなどの支援技術に示し、それ以外のコンテンツの利用を排除する (インタラクション可能な範囲を、モーダルのコンテンツに限定する)
AndroidのTalkBack以外の主要読み上げソフトでは動作している様子。
他のモーダルライブラリ
a11y-dialogというのがあるのを最近知った。
"a11y"を標榜しているのでよさそうではあるが、micromodalとの性能的な違いはよくわからず。
dialog 要素の解説記事
a11y-dialogのドキュメントから以下の記事を発見。
初回公開は2019年だが、2022年2月に更新されている。
ブラウザの実装を評価しつつも、a11y-dialogの使用を推奨している。
But, until the <dialog> is actually fully delivered, I personally suggest continuing to use trusted and robust custom dialogs. Or, if you polyfill the <dialog> element itself, you absolutely need to make sure it fully performs as expected for all users.
(↓Google翻訳を元にして訳)
ただし、
<dialog>
が実際に完全に配信されるまで、信頼できる堅牢なカスタムダイアログを引き続き使用することを個人的にお勧めします。または、<dialog>
要素自体をポリフィルする場合は、すべてのユーザーに対して期待どおりに完全に機能することを絶対に確認する必要があります。