Dialog と Popover #9
Intro
ここまで解説した仕様を踏まえ、いくつかの代表的なユースケースの実装について考えていく。
あくまで仕様の組み合わせ方についての解説であり、実装そのものの推奨ではない。
また、ここで紹介する仕様はまだ変更の可能性があり、かつ実装も揃っていないものがある点に注意
Toast
次は、Popover の源流にもなった、画面端にメッセージを表示するいわゆる Toast UI について考えてみる。想定するのは以下のようなものだ。
メッセージの性質によって、色やアイコンのスタイルを変えられ、同時に複数積み上げて表示できるといった仕様が一般的だ。
基本は もし内容のレイアウトで Flex や Grid を使いたい場合、 メッセージは共通ではなく、状況に応じて変更されるだろう。そこで、メッセージを表示する場所を用意する。まずはこれを これを HTML のテンプレートとして保持し、内容(message, icon, style etc)を変更しながら、使い回してさまざまなメッセージを表示することになるだろう。
注意点として 通知の用途を考えると、 逆に「ユーザにとって重要な通知」であれば、 今回は、一度表示した Popover の中身を動的に変えるケースは考えない。
また、即座に通知はするが、フォーカスを奪い、操作をすることは求めない。フォーカスを奪ってユーザの明示的な操作を求める場合は、内容の重要度に応じて 配置を右下にする場合は、 積み上げて複数表示する場合は、この 次にアニメーションを考える。
もともと Toast という名前であるように、下から飛び出すような実装が多いだろう。この場合は、 注意点として、 これは、 そこで さらに 今回は 以上の 各 Popover を表示するたびに、付与する ID や HTML
<div popover>
となる。また、複数のメッセージがあった場合に、他のが表示されても消えないよう、manual
を指定する。
<div popover="manual">
</div>
<div popover>
自体の display
を変えると扱いが少し面倒になるため、もう 1 つ Wrapper の <div>
を用意しておくと良いだろう。
<div id="toast" popover="manual">
<div style="display:flex">
</div>
</div>
manual
である以上、明示的な処理がないと Close されなくなるため、popovertarget
に ID を指定し popovertargetaction=hide
で閉じる UI を提供する。アイコンなどを用いた方が良いが、簡易化のために x
で代用する。
<div id="toast" popover="manual">
<div>
<button popovertarget="toast" popovertargetaction="hide" aria-label="close">x</button>
</div>
</div>
<p>
としてみよう。
<template>
<div id="toast" popover="manual">
<p></p>
<div style="display:flex">
<button popovertarget="toast" popovertargetaction="hide" aria-label="close">x</button>
</div>
</div>
</template>
popover
は dialog
と異なり対象の Role には影響を与えないため、これは単にテキストとボタンを持った <div>
が Top Layer に表示されただけの状態になる。この情報がなんであるか、ユーザにとってどのような重要度を持つ情報なのかについては、別途補完していく必要がある。この点に対しては、従来の「popover
を使わずに実装されていた Toast コンポーネント」も参考にできる。
role=status
か role=alert
を用いるのが一般的だろう。多くの「ユーザに通知があったことを伝えるが、作業を中断するほどでない」ケースでは、aria-live=polite
たる role=status
を用いる。また、もし他の role
に切り替える予定がないのであれば、同じく aria-live=polite
である <output>
を用いる方法も考えられる。
<template>
<div id="toast" role="status" popover="manual">
</div>
</template>
role=alert
を用いる。aria-live=assertive
であり、UA を通じてユーザには直ちに内容が伝わる。逆を言えば、これは多用すべきではないため、最小限に止めるべきだろう。
<template>
<div id="toast" role="alert" popover="manual">
</div>
</template>
role=dialog
/ role=alertdialog
を使うことになるが、この場合は <dialog>
の利用も視野に入ってくるため、今回はスコープ外とする。
CSS
<dialog>
で行ったのと同じように position: absolute
を指定する。
[popover] {
position: absolute;
top: auto;
right: 10px;
bottom: 10px;
left: auto;
}
bottom
を追加していくことになるだろう。
opacity
をトランジションするより、height
をトランジションすることで、下からニョキっと伸びるような表現ができる。
/* enable transitions */
[popover] {
transition:
display var(--duration) allow-discrete,
overlay var(--duration) allow-discrete,
height var(--duration);
}
:popover-open {
height: auto;
}
@starting-style {
:popover-open {
height: 0;
}
}
transition
で height
を指定しても、最終的な height
が auto
であると、従来はトランジションできなかった。
auto
, min-content
, max-content
のようなキーワードで指定され、内部のコンテンツを基準に値が決まる intrinsic-size
については、トランジションに指定できないという制限があったからだ。
auto
の代わりに具体的な値を指定するといった必要があったが、それではカバーできないケースがあるため、intrinsic-size
へのアニメーションが可能になるように策定されたのが interpolate-size
だ。値に allow-keywords
を指定することで、キーワード指定のサイズを用いたトランジションが可能になる。
:root {
interpolate-size: allow-keywords;
}
calc-size()
を用いると、intrinsic-size
を基準とした calc()
も可能になる。
:popover-open {
/* auto の計算結果が size 変数に入り、第二引数で計算した結果が反映される */
height: calc-size(auto, size + 1rem);
}
auto
にすればよいだろう。非表示に関しては特にアニメーションせず、パッと消えれば良いとすれば、以下のようになる。
:root {
interpolate-size: allow-keywords;
}
/* enable transitions */
[popover] {
transition:
display var(--duration) allow-discrete,
overlay var(--duration) allow-discrete,
height var(--duration);
}
:popover-open {
height: auto;
/* height: calc-size(auto, size); */
}
@starting-style {
:popover-open {
height: 0;
}
}
JS
<template>
を、メッセージの発生ごとに動的にクローンし、showPopover()
するコードを書いていくだけだ。
const clone = template.content.cloneNode(true)
// id, role, message などを埋め込む
document.body.appendChild(clone)
// 表示
document.querySelector('[popover]').showPopover()
bottom
の値などを変更しながら積み上げていくことになるだろう。
DEMO
動作するデモを以下に用意した。
- Popover Toast DEMO