Buttonについて - React Ariaの実装読むぞ
こんにちは、フロントエンドエンジニアの mehm8128 です。
それでは今日は Button について書いていきます。
useButton
とは
ボタンを作るための hook で、マウスやタッチ、キーボードによるインタラクションをサポートします。
使用例
ドキュメントからそのまま取ってきています。
function Button(props) {
let ref = useRef<HTMLButtonElement | null>(null);
let { buttonProps } = useButton(props, ref);
let { children } = props;
return (
<button {...buttonProps} ref={ref}>
{children}
</button>
);
}
本題
APG はこちらです。
usePress
について
useButton
で利用されているusePress
hook では様々な a11y 対応がされていて、ブログ記事にまとめられています。
簡単に概要をまとめます。
主にタッチデバイスへの対応の話が書かれていて、マウスでできる操作がタッチイベントだと難しかったり、:active
や:hover
の動作がユーザーの期待と一致しない場合があって UX が悪くなりがちだったりといった問題が挙げられています。
そこで React Aria では Pointer Events API が利用されています。この API はマウス、タッチ、ペンによる操作に対応していて、pointerType
プロパティによってどの機器によってイベントが発火されたのかも知ることができます。
usePress
ではこれを用いて上記の問題の解決や、その他タッチキャンセルやテキスト選択、キーボード操作時にキーを押しっぱなしにすることによるイベントの複数発火防止などにも対応しています。usePress
はuseButton
以外にもいくつかの hooks で用いられています。
実装を読みたい人はこちらから。実装を読むとかいうタイトルのアドベントカレンダーですが、僕はほんのちょっとしか読んでいません(読んだ部分については明日書きます)。
関連して、ホバーについては別の記事と、これを簡潔にまとめたまっつーさんの記事が参考になります。後者の記事には Pointer Events API のサンプルコードもあります。
isPending
について
こちらは hooks にはないのですが、React Aria Components に含まれている props です。
データの送信中などにボタンを一時的に pending にしておくことができます。
isPending={true}
のときはボタンにaria-disabled="true"
がつきます。また、即座に ProgressBar(role="progressbar"
のもの)をアクセシビリティツリーに含める必要があります。
https://react-spectrum.adobe.com/react-aria/Button.html#accessibility
progressbar
role はデフォルトでライブリージョンのように振る舞うので、isPending
になった瞬間に ProgressBar のラベルが読み上げられます。
と思っていたのですが、読み上げられなさそうで React Aria では手動で読み上げが実装されていました...。
ローディング状態になったときに if 文の 1 つ目のブロックが実行され、ローディング状態が解除されたときにelse if
のブロックが実行されます。
announce
関数ではLiveAnnouncer
という独自のオブジェクトが利用されていて、visually hidden なdiv
要素を用意しておいて、そこにaria-live
などをつけた要素を入れておきます。さらにその中に読み上げさせたいテキストをdiv
要素で追加する、もしくはaria-labelledby
でテキストを指定したければ、aria-labelledby
でテキストへの参照をつけたimg
role(おそらくaria-labelledby
がつけられる role ならなんでも OK)のdiv
要素を追加する、という実装になっています。
実際にレンダリングされる HTML を見た方が分かりやすいと思うので、大体こんな感じのコードです。
実際の HTML が見たい方はドキュメントのデモとか開発用 Storybook とかで HTML 内をdata-live-announcer
で検索かけると出てきます。
<div data-live-announcer="true" style="visually hiddenにするstyle">
<div role="log" aria-live="assertive" aria-relevant="additions">
<!--`announce`関数発火時にここに要素が追加される-->
</div>
<div role="log" aria-live="polite" aria-relevant="additions">
<!--例えばこんな感じ-->
<div>読み上げたいテキスト</div>
<!--もしくはこう-->
<div role="img" aria-labelledby="読み上げたいテキストの要素のid"></div>
</div>
</div>
ちなみにaria-
について補足しておくと、aria-live
は要素が更新されたときにスクリーンリーダーに読み上げさせるためのもので、値として、指定していないときと同じoff
、更新されたらすぐに通知するassertive
、他の読み上げが終わってから通知するpolite
を指定できます。今回はassertive
が指定されています。
aria-relevant="additions"
は要素が追加されたとき通知することを表し、他には要素が削除されたときに通知するremovals
などがあります。今回はaria-rive
やaria-relevant
をつけているdiv
タグの中に、読み上げさせたいタイミングで読み上げテキストを含む要素を一時的に追加するような運用をしているのでadditions
が指定されています。
まとめ
明日の担当は @mehm8128 さんで、 Link についての記事です。お楽しみにー
Discussion