Accessibility
アクセシビリティ
はじめに
カルーセルUIは、Webサイトのコンテンツを少ないスペースで魅力的に表現できる一方、致命的なアクセシビリティの問題を抱えています。例えば「スライドを変更して新しく表示された内容を読む」という基本的な動作でさえ、スクリーンリーダのみを通して行おうとすると、途端に容易なものではなくなります。
Splideは、すべてのユーザが快適にスライダーを使えるよう、アクセシビリティの向上に努めています。バージョン4からは、W3CのCarousel Design Patternに準拠したうえで、スクリーンリーダーが動的にコンテンツを読み上げられるようライブリージョンも導入しました。
ただし残念ながら、W3Cのデザインパターンも完全無欠とは言えません。
- 矛盾点がある
- すべてのスライダーに適用できるわけではない
たとえば、彼らのサンプルは「フェード」タイプに限定されていますが、これをそのまま「スライド」タイプのスライダーに適用すると壊れてしまいます。この例をそのまま採用してしまっているスライ ダーも多く、逆にアクセシビリティを損ねてしまっています。
以上を踏まえながら、このページでは、Splideがアクセシビリティ向上のために実装している機能などについて、くわしく解説していきます。
「基本型」と「タブ型」
W3Cのオーサリングプラクティスにおいて、スライダーはBasic(基本型)、Tabbed(タブ型)、およびGrouped(グループ型)の3つのタイプに分けられています。Splideのスライダーはこのうち、ページネーションが存在するかどうかを基準に、BasicかTabbedのいずれかの型を用いて実装されます。
型 | 説明 |
---|---|
Basic | ページネーションを持たないスライダーに対して適用される |
Tabbed | ページネーションを持つスライダーに対して適用される。ページネーション自体はタブパターンに準拠して実装され る |
Grouped | 該当なし |
タブ型は、スライダーをタブUIのように見なすため、それぞれのスライドをtabpanel
(タブパネル)、ページネーションをtab
(タブ)として実装する必要があります。さらにタブUIの仕様は、以下で説明するような、複雑なキーボード管理を要求します。
ランドマーク化
多くの場合、スライダーの中にはメインのコンテンツと関連性の高いものが含まれています。たとえば、バナー、ギャラリー、おすすめ記事、商品の一覧などがあげられ、基本的にはその中だけで情報が完結します。Splideは初期設定でスライダーをランドマークにすることにより、支援技術に対してそれが主要なコンテンツの一部であることを伝え、たとえばランドマーク間をジャンプするなどといった機能の恩恵をユーザが受けられるように しています。
ただし、メインのコンテンツと関連性のないものが含まれていたり、あるいは単に装飾目的のスライダーだったりする場合は、ランドマーク化することがふさわしくない場合もあります。そのようなときは、代わりにgroup
ロールを使用してください。
<
div
class
=
"splide"
role
=
"group"
aria-label
=
"..."
>
<
/
div
>
HTML
<div class="splide" role="group" aria-label="..."> </div>
HTMLではなく、オプションからでも変更できます。
new
Splide
(
'.splide'
,
{
role
:
'group'
}
)
;
JavaScript
new Splide( '.splide', { role: 'group' } );
キーボード操作
バージョン4からは、独自のキーボード操作(例えば矢印キーでスライダーを移動するなど)を有効にするオプションは、デフォルトで無効になりました。代わりに、タブのデザインパターンに必要なショートカットが有効化されています。
ショートカット
ページネーションがフォーカスを保有しているとき、Splideは次のようなキーボード操作を受け付けるようになります。
キー | 説明 |
---|---|
← | 横並びのページネーションにおいて、フォーカスを前のボタン (RTLでは次のボタン)に移動すると同時に、対応するスライドを表示する |
→ | 横並びのページネーションにおいて、フォーカスを次のボタン (RTLでは前のボタン)に移動すると同時に、対応するスライドを表示する |
↑ | 縦並びのページネーションにおいて、フォーカスを前のボタンに移動すると同時に、対応するスライドを表示する |
↓ | 縦並びのページネーションにおいて、フォーカスを次のボタンに移動すると同時に、対応するスライドを表示する |
Space | 対応するスライドを表示する |
Enter | 対応するスライドを表示する |
Home | フォーカスを最初のボタンに移動し、最初のスライドを表示する |
End | フォーカスを最後のボタンに移動し、最後のスライドを表示する |
基本的に、ページネーションのボタンがキーによってフォーカスされると、対応するスライドは自動的に表示されます。これはHaving selection follow focusと呼ばれ、「フォーカス追随の選択」と和訳されていますが、この訳ではなんのことだか分かりませんね。直訳すると「選択をフォーカスに追随させる」となりますので、すなわち「フォーカスと同時に対象を選択状態にする」ということです。
ただし、waitForTransition
が有効な場合は、フォーカスだけが先行する場合があります。この場合でも、Spaceなどで選択できますので、特に問題にはなりません。
keyboard
およびpaginationKeyboard
オプションによって、ショートカットを有効にするかどうかを変更できます。
ページネーションの方向
ページネーションの方向は、スライダーの方向を決めるdirection
オプションで決まります。例えば、direction
が'ttb'
の場合、実際にどう見えているかにかかわらず、Splideはページネーションを縦並びとして認識します。結果、aria-orientation
にはvertical
(縦)が設定され、フォーカスを移動するために↑や↓キーを監視します。
仮に、水平のスライダーに縦並びのページネーションを、あるいはその逆を実装したい場合は、paginationDirection
オプションで明示的に方向を指定してください。
new
Splide
(
'.splide'
,
{
direction
:
'ttb'
,
paginationDirection
:
'ltr'
,
}
)
;
JavaScript
new Splide( '.splide', { direction : 'ttb', paginationDirection: 'ltr', } );
ロービング・タブインデックス
Splideは、ページネーションのフォーカスをロービング・タブインデックス(roving tabindex)を用いて管理します。これは、タブ化されたインターフェースに求められる機能で、アクティブなボタンにはtabindex="0"
を、それ以外の非アクティブなボタンにはすべてtabindex="-1"
を割り当てることで実現されます。タブインデックスが状況に応じて変化するため、roving(さまよう)という名称がついたのだと思います(ラジオボタンなど、ほかのUIにも採用されます)。
これにより、例えば次のような利便性を得ることができます。
ページネーションから離れた後、 再度戻ってきた際、以前選択した位置から操作を再開できる
Tabを一度押すだけでページネーションを抜けられる。スライドが10枚ある場合、この機能がないページネーションでは10回押さないと抜けられない
次のスライダーを用いて、実際に試してみてください。次に進む矢印をクリックしてからTabを押すと、アクティブなボタンにフォーカスが移動します。そこから、←や→でページネーション内を移動できます。
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
ライブリージョン
非表示のスライドが表示された際、新たに表示されたコンテンツをスクリーンリーダーを通して読み取るのは、決して容易なことではありません。我々はそれを視覚情報を頼りに認識できますが、聴覚あるいは点字による情報に頼っているユーザにとって、ボタンとは別の場所が動的に更新されてしまうスライダーは、とても扱いにくい存在です。
これを解決するための最大の武器が、ほかならぬライブリージョンなのですが、厄介なことにaria-live="polite"
属性を割り当てるだけではうまく動作しません。なぜならば、一般的なスライダーの実装では、支援技術がコンテンツの変更を検出するためのaria-relevant
条件を満たすことができないからです。
詳細は割愛しますが、長い試行錯誤の結果、なんとか動かすことに成功しました🎉
ただし、自動再生が有効な場合、ライブリージョンはとても邪魔になります(ページを表示している限り、延々としゃべり続けます)ので、自動再生中はこの機能が無効化されます。停止すると、再度有効化されます。
なお、isNavigation
をture
にした場合、ライブリージョンは有効になりません。また、live
オプションを用いると、手動で無効にできます。
Windows Narrator、JAWS、NVDA、並びにVoice Overを用いてテストしましたが、すべてのスクリーンリーダで正しく動作することを保証することはできません(全リーダを全ブラウザでテストするのは、個人では不可能です😱)。
自動再生の制御(WCAG 2.2.1, 2.2.2)
制限時間があるコンテンツを利用する前に、利用者がその制限時間を解除できる — Timing Adjustable
自動更新する情報が、自動的に開始し、その他のコンテンツと並行して提示される場合、利用者がそれを一時停止、停止、もしくは非表示にする、又はその更新頻度を調整することのできるメカニズムがある。 — Pause, Stop, Hide
W3Cによる と、自動再生するスライダーは、以下の3つの機能を持つ必要があります。
- 自動再生を再生・停止するボタンを提供する
- スライダー内の要素がフォーカスした際は、自動再生を停止する
- スライダー上にマウスがある間は、自動再生を停止する
全盲、ロービジョン(光をまぶしく感じるなど)、巧緻性障害などのユーザは、コンテンツを読むのに十分な時間が必要です。自動再生を有効にする場合は、再生・停止用のボタンを同時に提供することを検討してください。Splideでは、マークアップを張り付けるだけで簡単にトグルボタンを実装できます。
他2点に関しては、pauseOnHover
とpauseOnFocus
が初期状態で有効になっているため、特別な実装は必要ありません。
視覚効果の低減(WCAG 2.3.3)
アニメーションが、機能又は伝達されている情報に必要不可欠でない限り、インタラクションによって引き起こされるモーションアニメーションを無効にできる。 — Animation from Interactions
ユーザの中には、アニメーションやトランジションなどの効果に対して、頭痛や吐き気を感じる人もいます(たとえば前庭運動障害を患うユーザなど)。Splideは、prefers-reduced-motion: reduce
を検出した際
speed
とrewindSpeed
を0
で上書きますautoplay
をpaused
で上書き、再生ボタンを押さない限り自動再生が行われないように変更します
この結果、スライドの遷移アニメーションは再生されず、瞬時に表示されるようになります。ただし、ドラッグやスワイプ後のトランジションだけは無効になりません。これを無効にしてしまうと、何が起こっているのかわからなくなってしまうため、「必要な」モーションであると判断しました。
Chromeの開発者ツールを使うと、この機能をテストできます。ツールの中にある"Rendering"タブの中から、"Emulate CSS media feature prefers-reduced-motion"という項目を探してください。
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
スライダーが単なる装飾目的で使用される場合を除き、スライドの移動を自動再生「だけ」に頼るのは避けてください。そうしないと、prefers-reduced-motion
が有効になった際、ユーザがスライドを変更する手段がなくなります。
フォーカスの可視化(WCAG 2.4.7)
キーボード操作が可能なあらゆるユーザインタフェースには、フォーカスインジケータが見える操作モードがある。 — Focus Visible
キーボードを利用しているユーザのため、フォーカス可能な要素に対して、フォーカス時の特別なスタイルを付加する必要があります(例えばアウトライン)。
キーボードユーザに対してのみこのスタイルを有効にするには、:focus-visible
疑似クラスを利用する必要があります。ただし
- IEはこのクラスをサポートしていません(IE自体がもうすぐ終了しますが)
- Safariは、最新バージョンのみこのクラスを実装しています(2022/03/15にリリース)
ゆえに、あまり気が進みませんでしたが、:focus-visible
を再現できるようなパッチを一時的に作成し、組み込むことにしました。バージョン4のテーマには、キーボードによってフォーカスを得た場合にのみ各ボタンを強調するようなスタイルが導入されています。
下の例において、前に戻るボタンを一度クリックしたのち、Tabを押してみてください。
もし独自のスタイルを当てる場合は、ルート要素が持つ.is-focus-in
クラスを利用してください。このクラスは、フォーカスがキーボードによってスライダー内に移動した際出現します。
/* :focus-visibleをサポートしているブラウザのためのCSS */
.splide__arrow:focus-visible
{
outline
:
3
px
solid
tomato
;
}
/* その他のブラウザのためのスタイル */
.splide.is-focus-in
.splide_arrow:focus
{
outline
:
3
px
solid
tomato
;
}
CSS
/* :focus-visibleをサポートしているブラウザのためのCSS */ .splide__arrow:focus-visible { outline: 3px solid tomato; } /* その他のブラウザのためのスタイル */ .splide.is-focus-in .splide_arrow:focus { outline: 3px solid tomato; }
このクラスは、フォーカスを失ってもすぐには取り除かれませんので、必ず:focus
疑似クラスとともに使用してください。
フォーカス可能な要素(WCAG 1.3.1)
何らかの形で提示されている情報、構造、及び関係性は、プログラムによる解釈が可能である、又はテキストで提供されている。 — Info and Relationships
Splideは、スライダーの表示領域に完全に収まっていないスライドに対してaria-hidden
属性を割り当て、スクリーンリーダ、あるいはその他の支援技術がそれらのスライドを認識しないようにしています。ところが、スライド自体に<input>
や<textarea>
などのフォーカス可能な要素が含まれている場合、Tabにより依然として、隠れた要素にフォーカスを移動できてしまいます。このとき、スクリーンリーダはそれが何なのかの説明をしてくれません。
この問題に対応するため、Splideは隠れているスライドが含む以下の要素に対してtabindex="-1"
を適用し、一時的にフォーカスできないようにしています。
<a>
<button>
<textarea>
<input>
<select>
<iframe>
ほとんどの場合この対応でうまくいきますが、上記に漏れる以下の要素が含まれている場合は追加対応が必要です。
<area>
<audio controls>
<summary>
<video controls>
contenteditable
属性が有効になっている要素tabindex="0"
を持った要素
どの要素に対してtabindex="-1"
を適用するかは、focusableNodes
オプションの値が決定しています。この値はセレクタですので、次のようにして検索対象を増やしたり、逆に不必要な要素を減らしたりできます。
{
focusableNodes
:
'a, button, ..., area, [contenteditable]'
,
}
JavaScript
{ focusableNodes: 'a, button, ..., area, [contenteditable]', }
ただし、残念ながらtabindex="0"
が設定された要素が含まれる場合、このオプションだけではうまく対応できません。これは、-1
に設定した後に再びスライドが表示された際、0
(あるいはその他の整数値)に戻す処理が必要になるためです。スライダーの中でtabindex
を用いてフォーカスを制御することはないと信じたいですが、仮にそのような対処が必要になった場合は、visible
およびhidden
イベントを利用すればインデックスの切り替えを実装できます。
ARIA属性(WCAG 4.1.2)
すべてのユーザインタフェース コンポーネント (フォームを構成する要素、リンク、スクリプトが生成するコンポーネントなど) では、名前 (name) 及び役割 (role) は、プログラムによる解釈が可能である。又、状態、プロパティ、利用者が設定可能な値はプログラムによる設定が可能である。そして、支援技術を含むユーザエージェントが、これらの項目に対する変更通知を利用できる。 — Name, Role, Value
矢印のボタンやページネーションなど、インタラクション可能なすべての要素には、適切なARIA属性が割り当てられます。ただし、ラベル等に割り当てられているテキストは英語ですので、対象ユーザに合わせて適宜翻訳してください。
基本型スライダー
ルート
role | タグが |
---|---|
aria-roledescription |
|
トラック
aria-live | ライブリージョンが有効の場合 |
---|---|
aria-atomic | ライブリージョンが有効の場合 |
aria-busy | ライブリージョンが有効の場合、トランジション終了時に |
各スライド
role |
|
---|---|
aria-roledescription |
|
aria-label |
|
aria-hidden | ビューポートに収まりきっていないすべてのスライドに |
isNavigation
が有効の時は、次のように変化します。
role |
|
---|---|
aria-roledescription | インタラクティブ要素なので割り当てられない |
aria-hidden | ビューポートに収まりきっていないすべてのスラ イドに |
aria-controls | 同期しているスライダーのすべてのID |
aria-label | "Go to slide %s" |
矢印
aria-controls | トラック要素のID |
---|---|
aria-label | 前へ戻る矢印には"Previous slide"または"Go to last slide"、次に進む矢印には"Next slide"または"Go to first slide" |
disabled | それ以上進めなくなったときに適用される |
再生・停止ボタン
aria-controls | トラック要素のID |
---|---|
aria-label | 自動再生中は"Pause autoplay"、再生中は"Start autoplay" |
タブ型スライダー
基本型を踏襲しつつ、次の要素に対する属性が変わります。
各スライド
role |
|
---|---|
aria-roledescription |
|
aria-label |
|
aria-hidden | ビューポートに収まりきっていないすべてのスライドに |
ページネーション
role |
|
---|---|
aria-label | "Select slide to show". |
ページネーションリスト
role |
|
---|
ページネーションの各ボタン
role |
|
---|---|
aria-controls | 対応するすべてのスライドのID |
aria-label | "Go to slide %s"または"Go to page %s". |
aria-selected | アクティブなボタンに |