【dialog】クリックイベントで指定ボタンへオートフォーカス

IEが召されてdialogが気兼ねなく使えるようになったので、モーダル実装にチャレンジしてみてます。
その際につまったのが「閉じるボタンへのオートフォーカス」。
あーしてもこーしても、フォーカスがまったくあたらない🤤
(先に言ってしまうと、ほんとうにしょうもない勘違いのせいでしたてへ)

実装目標

  • モーダルを開いたら、フォーカスをモーダル内の閉じるボタンへ移したい

つまったところ

解決前の簡易デモはこちら。

See the Pen dialog - autofocus (NG Ver.) by Yurika (@yurika1202) on CodePen.

動作は一応しているものの…閉じるボタンへのオートフォーカスが達成できていないので、モーダルを閉じるまでの動きに1アクション追加されてしまいます。よろしくない🙄
フォーカスを強制的にあてるため、閉じるボタンにfocus()メソッドを呼び出していますが効いていません。

解決までの糸口

ひとまずこちらが解決後の簡易デモです。

See the Pen dialog - autofocus (OK Ver.) by Yurika (@yurika1202) on CodePen.

モーダルを開くと、きちんと閉じるボタンへフォーカスが当たっています
結論からいうと、モーダル開閉にはdialogのメソッドを使えば万事解決でした。無知。
そこにいたるまでオロオロ記録です…

setTimeout()の上書き

まずググってたどりついた先は、setTimeoutの中でfocus()メソッドを呼び出す方法。

sample.js
setTimeout(() => {
  this.closeBtn.focus();
}, 0);

一応フォーカスは当たるようになったものの…

  • setTimeoutを使用しての解決法は、昔のFirefoxでのバグへの対処だったりで使われているもの?(他記事もググってみたけど、どれも古い記事ばかり) 【js】firefoxでfocusイベントが動かない - Qiita
  • モーダルを開くタイミングによってはフォーカスが当たらない

ただフォーカスはあてれるようになったので、ひとまずsetTimeoutを使いつつ解決策をさらに探ってみることに。

visibilityではなく、displayで要素の表示を制御

先ほどの腑に落ちないところ2つ目の「モーダルを開くタイミングによってはフォーカスが当たらない」をどうにかしてみるため、原因を考えてみる。
おそらく非表示だった要素を表示させたとき(モーダルを開いたとき)に、DOMがレンダリングされるタイミング次第でフォーカスの挙動が変わっている。
ので、試しにvisibility:hidden;ではなく、display:none;に変更してみる。

フォーカスあたるー--!
どんなときでもフォーカスあたるー--!

だがしかし、表示時にアニメーションつけたいしJSではなくtransitionでなんとかしたいごにょごにょ…
なのでもうちょっと別方法を考えてみる。

dialogの開閉はメソッドが用意されている

なんか詰まってきたので、原点回帰として改めてMDNを読んでみる。
…モーダル開閉のためのメソッドあるやないかーい🙃
…モーダルにつけるopen属性も記述法違うやないかーい🙃
書き直してみると上手くフォーカスもあたる。

sample.js
openBtn.addEventListener('click', () => {
  dialog.showModal();
});

closeBtn.addEventListener('click', () => {
  dialog.close();
});
  • showModal()
    モーダルを開く。
    モーダル外へのアクセスが不可になり、Escapeキーで閉じることが可能。
    疑似要素::backdropで背景も自動でつけてくれるし、複数モーダルがあった場合でも常に最上部へ表示してくれる。
    モーダル外のコンテンツのスクリーンリーダの読み上げもしない。
  • show()
    モーダルを開く。
    モーダル外へのアクセスが可能。
    出番はモーダル外に閉じるボタンがあったりするとき?(あるのか?)
  • close()
    モーダルを閉じる。
sample.html
<dialog class="dialog" open>
  <button class="closeBtn" type="button">Close</button>
  <p>Hello!!!</p>
</dialog>

open属性については、showModal()またはshow()メソッドが実行されたタイミングでdialogへ付与されるので、特にJSなどで操作する必要なし。

transitionで表示アニメーションさせるには

フォーカス問題は解決されたのですが、dialogはブラウザのデフォルト設定としてdisplay:none;で表示・非表示をコントロールさせているので、これをどうにかしたい!

user agent stylesheet
dialog {
  display: block;
  ...
}
dialog:not([open]) {
  display: none;
}

transitionを効かせるため、displayblockへ上書きします。
そしてvisibilityopacityで非表示に。

sample.css
dialog {
  visibility: visible;
  opacity: 1;
  transition: visibility 0.4s ease-out, opacity 0.4s ease-out;
  ...
}
dialog:not([open]) {
  display: block;
  visibility: hidden;
  opacity: 0;
}

これで実行してみると、アニメーションは効くもののオートフォーカスが無効に…🤔
う~んと思ったのですが、よくよく考えてみるとvisibilityプロパティにtransitiondurationを仕掛けていたので、可視状態になるまでに時間がかかっている!
つまり完全に可視状態にならないとフォーカスはあたらないので、duration0に設定しなおしてみる。

sample.css
dialog {
  visibility: visible;
  opacity: 1;
  transition: opacity 0.4s ease-out; /* 変更点 */
  /* transition: visibility 0s ease-out, opacity 0.4s ease-out;と同意味 */
  ...
}
dialog:not([open]) {
  display: block;
  visibility: hidden;
  opacity: 0;
}

これで無事フォーカスも当たるし、transitionも効いてる~~!

See the Pen dialog - autofocus (OK Ver.) by Yurika (@yurika1202) on CodePen.

おわり

これでゴールとする「モーダルを開いたら、フォーカスをモーダル内の閉じるボタンへ移したい」を実装することができました
だいぶ遠回りはしたけど、よき勉強になりました…(遠い目)
とりあえず次からは公式ドキュメントを穴が開くほど見るようにします。

それにしてもdialog要素が気兼ねなく使えるようになったおかげで、実装側がアクセシビリティに悩む時間が軽減されてて感動…

それでは、 ☁️ぼんっ

参考サイト