ポップオーバー API で JavaScript を使わずにポップアップを表示する
Chrome 114 から追加されたポップオーバー API を使うと、JavaScript を使わずに簡単にポップアップを表示することができます。
Chrome 114 から追加された ポップオーバー API を使うと、JavaScript を使わずに簡単にポップアップを表示できます。ただポップアップとして表示・非表示を切り替えられるだけでなく、以下のような複雑な要素もデフォルトでサポートしています。
- ポップアップの外側をクリックするとポップアップが閉じる
- Escape キーを押すとポップアップが閉じる
- 最も大きな z-index の上に表示される(top layer)
- ポップアップが非表示になったとき、ポップアップ内にフォーカスがある場合前にフォーカスしていた要素にフォーカスが戻る
- 1 度に 1 つのポップアップしか表示できない(入れ子のポップアップは例外)
- ポップオーバーの表示・非表示を切り替える要素に
expanded
ステートがアクセシビリティツリーに公開される(aria-expanded
属性と同等)
最も簡単なポップアップは以下のコードで実装できます。
<button popovertarget="popover">ポップアップを表示</button>
<div popover id="popover">ポップアップの中身</div>
ポップオーバーの基本的な使い方
ポップオーバーはコンテンツが表示される要素(=ポップオーバー要素)と、ポップオーバーの表示を切り替える要素(=コントロール要素)の 2 つの要素で構成されます。
ポップオーバー要素には popover
属性を指定します。また、ポップアップ要素とコントロール要素を関連付けるために id
属性を持っている必要があります。
<div popover id="popover">ポップアップの中身</div>
popover
属性が指定された要素はユーザーエージェントスタイルシートによって display: none;
が適用されます。popover
属性が指定された要素ははじめは非表示になっているということです。
値なしで popover
属性を指定している場合、popover=auto"
をしているのと同じになります。popover
属性は auto
と manual
の 2 つの値を取ることができます。auto
の場合、ポップオーバーとして必要な機能を自動的に提供してくれるので、基本的には auto
を指定するのが良いでしょう。
auto
- ポップオーバーを「簡単に解除(light-dismiss)」できる。つまり、ポップオーバーの外側をクリックするか、Escape キーを押すとポップオーバーが閉じるようになる。
- 1 度に 1 つのポップオーバーしか表示できない。すでにポップアップが表示されている状態で他のポップアップを表示すると、すでに表示されているポップアップが閉じられる
manual
- ポップオーバーを「簡単に解除」できない。
- 1 度に複数のポップアップを表示できる
コントロール要素には、popovertarget
属性を指定します。この属性の値は、ポップオーバー要素の id
属性の値と同じにする必要があります。
<button popovertarget="popover">ポップアップを表示</button>
<di popover id="popover">ポップアップの中身</div>
デフォルトでターゲット要素の挙動はトグルボタンとなっています。つまり、ターゲット要素をクリックするとポップアップが表示され、もう一度クリックするとポップアップが非表示になります。この挙動を変更するには、popovertargetaction
属性を指定します。popovertargetaction
属性の値は toggle
, show
, hide
の 3 つの値を取ることができます。
toggle
:デフォルトの挙動。ターゲット要素をクリックするとポップアップが表示され、もう一度クリックするとポップアップが非表示になるshow
:ターゲット要素をクリックするとポップアップが表示されるhide
:ターゲット要素をクリックするとポップアップが非表示になる
以下の例では、1 つ目のボタンはポップアップを表示するためのボタン、2 つ目のボタンはポップアップを非表示にするためのボタンと、ボタンごとに役割を分けています。
<button popovertarget="popover" popovertargetaction="show">
ポップアップを表示
</button>
<button popovertarget="popover" popovertargetaction="hide">
ポップアップを非表示
</button>
<div popover id="popover">ポップアップの中身</div>
ポップオーバーの CSS
ポップオーバーには以下の CSS 擬似クラス, 擬似要素が用意されています。
:popover-open
:ポップオーバーが表示されているときに付与される::backdrop
:ポップオーバーの背景として表示される要素
[popover]:popover-open {
/* ポップオーバーが表示されているときのスタイル */
boder-color: orange;
}
[popover]::backdrop {
/* ポップオーバーの背景として表示される要素のスタイル */
background-color: rgba(0, 0, 0, 0.5);
}
JavaScript からポップオーバーを制御する
JavaScript でポップオーバーの表示・非表示を切り替える
ポップオーバー API は HTMLElement.popover 要素を通して JavaScript から制御できます。ポップオーバーの表示・非表示を切り替えるメソッドがいくつか用意されています。
showPopover()
:ポップオーバーを表示するhidePopover()
:ポップオーバーを非表示にするtogglePopover()
:ポップオーバーの表示・非表示を切り替える
JavaScript を利用した例として、ボタンをマウスオーバーしたときにポップオーバーを表示するようにしてみましょう。ポップオーバーが既に表示されているときに showPopover()
を呼び出すと InvalidStateError
が発生します。そのため、elemebt.matches() メソッドを使用して、ポップオーバーが表示されているかどうかを判定しています。
ポップオーバーが表示されている場合には :popover-open
擬似クラスが付与されるので、これを利用して判定しています。
const button = document.querySelector("button");
const popover = document.querySelector("#popover");
button.addEventListener("mouseenter", () => {
// ポップオーバーが既に表示されている場合は何もしない
if (popover.matches(":popover-open")) return;
popover.showPopover();
});
button.addEventListener("mouseleave", () => {
// ポップオーバーが既に表示されていない場合は何もしない
if (!popover.matches(":popover-open")) return;
popover.hidePopover();
});
beforetoggle
イベントでポップオーバーの表示・非表示の前に処理を実行する
もう 1 つ JavaScript を利用した例として、ポップオーバーが表示された後トーストのように数秒経過したら自動で閉じるように変更してみましょう。beforetoggle
イベントを購読することで、ポップオーバーの表示・非表示の前に処理を実行できます。beforetoggle
イベントのコールバック内で、3 秒後にポップオーバーを非表示にするようにしています。
const popover = document.querySelector("#popover");
popover.addEventListener("toggle", (event) => {
setTimeout(() => {
if (event.target.matches(":popover-open")) {
event.target.hidePopover();
}
}, 3000);
});
入れ子のポップオーバー
popover="auto"
を指定した場合、既にポップオーバーが表示されている状態で他のポップオーバーを表示すると、既に表示されているポップオーバーが閉じられます。その例外として、入れ子のポップオーバーは閉じられません。
ポップオーバーを入れ子にする方法は 3 つあります。
- DOM で直接子要素にする
- コントロール要素が入れ子の関係にある
anchor
属性でポップオーバーの親要素を指定する
DOM で直接子要素にする
DOM 上であるポップオーバーが別のポップオーバーの直接の子要素になっている場合、入れ子のポップオーバーとして扱われます。
コントロール要素が入れ子の関係にある
child ポップオーバーをコントロールする要素が、parent ポップオーバーのコンテンツ内に存在する場合、DOM 上で入れ子の関係となっていなくても、入れ子のポップオーバーとして扱われ、child ポップオーバーを開いても parent ポップオーバーは閉じられません。
anchor
属性でポップオーバーの親要素を指定する
ポップオーバー要素が anchor
属性で別のポップオーバー要素を指定していた場合、ポップオーバー要素は入れ子の関係とみなされます。 anchor
属性は祖先となる要素を id で指定します。
アクセシビリティ上の観点
popover
属性または popovertarget
属性が指定された要素はどのような要素にも指定でき、セマンティクスに影響を与えることはありません。つまり、新たなロールが割り当てられることはないということです。例えば <article popover>
というように popover
属性を指定しても、article
要素のロールは変わらず article
ロールのままです。
ポップオーバーをコントロールする要素はキーボードでも操作可能にするために、<button>
要素で実装するのが好ましいでしょう。ポップオーバーのコンテンツには、ポップオーバーを使用する状況に応じて適切なロールを割り当てることが求められます。
ポップアップをトリガーする要素はポップオーバーの表示・非表示を切り替える要素に expanded
ステートがアクセシビリティツリーに公開されます。これは aria-expanded
属性が指定されているのと同じです。ポップオーバーが開いている場合には true
、閉じている場合には false
が設定されます。
また、popover="true"
を指定している場合にはポップオーバーとして必要な機能が自動的に提供されます。
- ポップオーバーの外側をクリックするか、Escape キーを押すとポップオーバーが閉じるようになる。
- すでにポップアップが表示されている状態で他のポップアップを表示すると、すでに表示されているポップアップが閉じられる
- ポップアップが非表示になったとき、ポップアップ内にフォーカスがある場合前にフォーカスしていた要素にフォーカスが戻る
上記のような機能は通常 JavaScript を用いて実装する必要があり、意外と適切に実装するのが難しいのではじめから提供されているポップオーバー API を使用することは大きなメリットと言えるでしょう。
まとめ
- ポップオーバーのコンテンツには
popover
属性を指定する。ポップオーバーの表示・非表示を切り替える要素にはpopovertarget
属性をポップオーバーのコンテンツのid
属性の値と同じにする - ポップオーバーが表示されているときには
:popover-open
擬似クラスが、ポップオーバーの背景として表示される要素には::backdrop
擬似要素が付与される showPopover()
、hidePopover()
、togglePopover()
メソッドを使用して JavaScript からポップオーバーの表示・非表示を切り替える。beforetoggle
イベントを購読することで、ポップオーバーの表示・非表示の前に処理を実行することができる- できる入れ子のポップオーバーの親要素は子要素が表示されても閉じられない
- ポップオーバー API はデフォルトでアクセシブルな実装になっている。セマンティクスは変わらないので利用者によって定義する必要がある