本文へ

開発記録

アバターの dialog における Escape・背景クリック・フォーカスの復元

アバターの拡大操作を例に、標準のモーダル動作、背景クリック、閉じる経路、フォーカスの復元の分担を整理し、画像自体のクリックで誤って閉じないことをブラウザー操作で確認した記録です。

林予晴
公開
  • HTML
  • Dialog
  • キーボード操作
  • 操作の検証

アバターを画面中央に拡大表示することは、この操作で一番難しい部分ではありません。画像が表示された後にキーボードフォーカスがどこへ移るのか、画像をクリックすると誤って閉じないか、Escape を押した後にどこへ戻るのか。そこまで含めて、操作が完結するかどうかが決まります。

このサイトのプロフィールページでは、標準の <dialog> を使ってアバターを拡大しています。このノートでは、開く経路と閉じる経路を分けて読み、ローカルブラウザーでも実際に操作しました。確認したかったのは、単に「画像が表示された」ことではありません。読者が元の操作要素から入り、また同じ位置へ戻れることです。

モーダルを開くには、open を付けるだけでは足りません

アバターを拡大する入口は、アクセシブルな名前を持つ標準のボタンです。起動すると、スクリプトはまず、ページに表示している画像の参照先と alt を dialog に設定し、その後で showModal() を呼び出します。

これは、単に一つのブロックを表示状態にすることとは、担う役割が異なります。MDN は、showModal() で開いた dialog はモーダルとして動作し、外部のコンテンツが inert になると説明しています。標準のモーダルには、Escape で閉じる既定の動作もあります。[1]

そのため、画面にオーバーレイがあるだけで、モーダルだとは判断できません。オーバーレイは見せ方であり、キーボードやフォーカスについては、実際の動作が必要です。

現在の拡大表示には、画像と閉じるボタンだけがあります。今回、アバターのボタンから Enter で開くと、フォーカスは閉じる操作要素に移りました。MDN にも showModal() のフォーカス処理について説明がありますが、今後フォーカス可能な内容を増やすなら、初期フォーカスに適した位置を改めて判断する必要があります。今回の結果を、すべての dialog に当てはまるフォーカス順序として扱うことはできません。[1]

Escape と閉じるボタンは、同じ終了処理につなげます

この dialog では、すべてのキー入力を横取りする Escape ハンドラーを別に書いていません。標準のモーダルの既定動作を残し、画面上の閉じるボタンは close() を呼び出します。[1]

二つの経路は入力こそ異なりますが、終了後に必要な状態は同じです。dialog が閉じ、フォーカスが拡大操作の入口へ戻れることです。

そこで、フォーカスの復元は、閉じるボタンの click 処理だけではなく、dialog の close イベントで行います。復元処理がボタンにしか結び付いていなければ、Escape や、ほかに許可された閉じ方では、同じ処理を通らない可能性があります。

今回は Escape と閉じる操作要素をそれぞれ試し、どちらでも dialog が閉じ、フォーカスが元のアバター拡大ボタンへ戻りました。片方の経路だけを試すよりも、現在の役割分担を確かめられます。

背景クリックで閉じる前に、何をクリックしたかを分けます

::backdrop はモーダルの後ろに暗い背景を付けられますが、この CSS だけで「外側をクリックすると閉じる」動作が実装されるわけではありません。このサイトでは、dialog の click リスナーで背景クリックを扱い、まず次の条件を確認しています。

if (event.target === dialog) dialog.close()

この条件は省けません。画像と閉じるボタンはどちらも dialog の中にあり、click が外側へバブリングすることがあります。dialog が click を受け取るたびに閉じるようにすると、読者が画像自体をクリックしても閉じてしまいます。

レイアウトに関わる細部もあります。現在は dialog 本体が表示領域全体を覆い、画像のコンテナーが中央にあります。このノートでいう「背景クリック」は、実際には dialog 本体の余白部分をクリックすることです。標準の ::backdrop が外側のクリックによる閉じる動作を自動で提供している、という意味ではありません。イベントのターゲットと、各要素が覆う範囲を合わせて見る必要があります。

ローカルでの確認では、画像の中央と、表示領域の隅にある余白部分をそれぞれクリックしました。前者では dialog が開いたままで、後者では閉じ、フォーカスがアバターの入口へ戻りました。両方の位置を試す必要があります。そうしないと、「閉じられる」ことだけを確認し、「閉じるべきでない場所」を見落としやすくなります。

フォーカスの戻り先は推測せず、起動位置を記録します

スクリプトは、開く前に、その時点の document.activeElement を記録します。閉じるときには、その記録した要素へフォーカスを戻します。

現在の操作では、アバターのボタンから開いたら、閉じた後も同じボタンへ戻ります。読者は、ページの先頭から先ほどの位置を探し直す必要がありません。

ただし、複雑な dialog もすべて同じ方法だけで足りるわけではありません。操作後に起動元の要素が削除される、フォーカスできなくなる、あるいは閉じた後に作業の次の段階へ進むべき場合には、別に移動先を定義する必要があります。これらは今後考えられる設計上の状況であり、このサイトで現在すでに対応している機能ではありません。

今回確認できるのは、元の入口が残っている場合です。Tab 操作も一度試し、フォーカスはこのモーダルの中にとどまりました。この観察だけで、すべてのフォーカス循環、スクリーンリーダー、モバイルでの支援機能を使った操作の受け入れ確認に代えることはできません。

拡大する内容は、ページ上の画像と一致させます

現在はプロフィール画像を1枚だけ表示しているため、拡大時にもページ上の同じ画像を表示します。

現在のスクリプトは、その1枚の画像から src と alt を同時にコピーしています。ここでの alt は、拡大画面用の文言として別に管理するのではなく、ページ上の画像に付けた説明をそのまま使います。

今回は、画像を拡大し、dialog 内の画像の参照先と alt がページ上の画像と一致することを確認しました。これが示すのは、その操作で二つの表示箇所が同じデータを使っていたことだけです。画像の内容説明そのものが、すべてのアクセシビリティ審査を通ったという意味ではありません。

画像の用途、サイズ、読み込み失敗には、それぞれ別の役割があります。詳しくは、画像の alt・サイズ・読み込み・失敗時の表示を一緒に設計する理由で扱っています。このノートは、画像がすでに存在する状態で、拡大操作にどう入り、どう抜けるかに絞っています。

今回実際に操作した経路

今回は Windows 上のローカルのヘッドレス Edge ブラウザーを使い、ブラウザーの入力イベントを通じてプロフィールページを操作しました。クリックの代わりに、実装の開閉ハンドラーを直接呼び出したわけではありません。

操作 今回確認した結果
アバターの入口にフォーカスを当てて Enter を押す dialog が開き、閉じるボタンへフォーカスが移りました
Escape を押す dialog が閉じ、アバターの入口へフォーカスが戻りました
画像の内容をクリックする dialog は開いたままでした
モーダル内で Tab を一度押す フォーカスは dialog 内にとどまりました
閉じるボタンをクリックする dialog が閉じ、アバターの入口へフォーカスが戻りました
dialog の背景の余白部分をクリックする dialog が閉じ、アバターの入口へフォーカスが戻りました
原図を拡大表示する 拡大画像の参照先と alt は、ページ上の画像と一致しました
ページを離れてから、ブラウザーの履歴で戻る その一回では BFCache が使われ、拡大、Escape、フォーカスの復元を引き続き操作できました

これは一回のローカルでの機能観察であり、主観的な見た目の受け入れ確認ではありません。すべてのブラウザー、何度も続けて履歴で戻る操作、支援技術を網羅したものでもありません。JavaScript がなくても、元のアバターと紹介内容は残ります。ただし、この拡大動作にはスクリプトが必要です。静的な内容を読めることを、スクリプトなしでも操作が成立していることと同一視はできません。

こうした小さな操作でも、完了条件には「閉じた後にどこへ戻るか」まで含めます。「クリックすると表示される」だけでは終わりにしません。画像が表示されるのは、操作の途中の段階にすぎないためです。

出典

[1] https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog