Accessibility
アクセシビリティ
はじめに
Splideは、W3C Carousel Conceptsが示す基準になるべく添うよう配慮しながら、スライダーのアクセシビリティ向上を図っています。このガイダンスは、次のような4つの基本要件を提示しています。
- キーボードあるいは音声入力を使っているユーザが、個々のスライドを移動できること
- スクリーンリーダを用いているユーザが、現在どのスライドが表示されており、かつどのようにすれば別のスライドに移動するかを理解できること
- ものの動きに気がとられてしまう人のために、アニメーションを停止できる手段を提供すること
- スライド内のコンテンツを読むのに時間がかかってしまう人のために、アニメーションを停止できる手段を提供すること。同時に、スライド内のコンテンツを読むための十分な時間を確保すること
自動再生の機能により、後半2つの問題はすでに解決されています。W3Cは再生・停止ボタンの提供を勧めていますが、配置するかどうかはご自身で決めてください。
以下、前半の2点について、Splideではどのような対応がなされているかについて説明していきます。
キーボード操作
ショートカット
ユーザは、矢印キーを用いてスライダーを操作できます。
ショートカット | 説明 |
---|---|
← | LTRでは前の、RTLでは次のページに移動 |
→ | LTRでは次の、RTLでは前のページに移動 |
↑ | TTBで前のページに移動 |
↓ | TTBで次のページに移動 |
デフォルトでは、Splideはdocument
のkeydown
を監視しているため、仮に1ページ内に複数のスライダーが配置されている場合、すべてが同時に反応します。もしこの動作が好ましくない場合、keyboard
オプションを'focused'
に設定すると、アクティブ要素(フォーカスを持った要素)を内包したスライダーのみが反応するようになります。なお、このときスライダーのルート要素には自動的にtabindex="0"
が追加されます。これはスライダーが、フォーカスできる要素を一つも持っていない可能性への対策です。
Intersectionエクステンションを利用すると、スライダーがビューポート内にある場合にのみキー入力を受け付けるように設定できます。
タブキーによる操作
ユーザはTabキーを用いて、現在見えているスライド、矢印ボタンおよびページネーションを移動できます。
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
slideFocus
オプションをfalse
にすればスライドへのフォーカスを無効にできますが、この場合は最初に引用した要件の1つ目を満足できなくなりますので注意してください。
フォーカスされた要素を強調表示するには、:focus
疑似セレクタを利用してスタイルを追加する必要があります。
ページネーション
このW3Cのチュートリアルでは、キーボード、あるいはそのほかの支援技術を使用しているユーザのために、ページネーションのボタンが押された際、対応するスライドにフォーカスを戻すことを勧めています。
ユーザがこれらのナビゲーションボタンを選択した際、選択された項目に対してフォーカスを設定すべきです。このケースでは、スライダーが変化、あるいは移動した後、
current
クラスを持つ<li>
要素に対してフォーカスを移動する必要があります。これにより、キーボードあるいは支援技術を使用するユーザは、より簡単にスライダーを操作できるようになります。
Splide v2にはこのような機能はありませんでしたが、v3で実装されました。下のスライダーのページネーションをクリックして、フォーカスの移動を確かめてみてください。
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09
フォーカス可能な要素
Splideは、スライダーの表示領域に完全に収まっていないスライドに対してaria-hidden
属性を割り当て、スクリーンリーダ、あるいはその他の支援技術がそれらのスライドを認識しないようにしています。ところが、スライド自体に<input>
や<textarea>
などのフォーカス可能な要素が含まれている場合、Tabにより依然として、隠れた要素にフォーカスを移動できてしまいます。
Lighthouseがこのような不親切な要素を検出すると、以下のような警告を発し、是正を促します。
[aria-hidden="true"]
が設定された要素の中にフォーカス可能な子孫要素を配置してしまうと、スクリーンリーダなどの支援技術を使用するユーザがこれらの要素を使用できなくなります。
この問題に対応するため、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属性
矢印のボタンやページネーションなど、インタラクション可能なすべての要素には、適切なARIA属性が割り当てられます。ただし、ラベル等に割り当てられているテキストは英語ですので、ユーザ層に合わせて適宜翻訳してください。
各スライド
aria-hidden | ビューポートに収まりきっていないスライドに対して |
---|
矢印
aria-controls | トラック要素のIDが設定される |
---|---|
aria-label | 「前」の矢印には"Previous slide(前のスライドへ)"または"Go to last slide(最後のスライドへ)"が、「次」の矢印には"Next slide(次のスライド)"または"Go to first slide(最初のスライドへ)"が割り当てられる |
また、矢印はそれ以上進めなくなった際、disabled
属性が付与されます。
ページネーション
aria-controls | スライドのID。複数枚表示される場合、すべてのIDが対象となる |
---|---|
aria-label | "Go to slide %s(スライド%sへ)"または"Go to page %s(ページ%sへ)"が割り当てられる |
再生・停止ボタン
aria-controls | トラック要素のIDが設定される |
---|---|
aria-label | 再生ボタンには"Start autoplay(自動再生を始める)"が、停止ボタンには"Pause autoplay(自動再生を中断)"が割り当てられる |
ナビゲーション
isNavigation
オプションを有効にした場合、スライダー自身がほかのスライダーを制御するナビゲーションとしてふるまいます。このようなスライダーに対しては、リスト要素にmenu
ロールが、各スライドにはmenuitem
ロールが割り当てられます。
リスト要素
role |
|
---|
各スライド
role |
|
---|---|
aria-controls | コントロール対象となるスライダーのID。対象が複数の場合は、すべてのIDが含まれる |
aria-label | "Go to slide %s(スライド%sへ)"が割り当てられる |
アクティブスライドの通知
W3Cの提案の中で唯一、WAI-ARIA regionを利用した「アクティブスライドの通知」には対応していません。
WAI-ARIAのライブリージョンを用いて、現在表示されている項目が何なのかをスクリーンリーダに知らせて下さい。
しかしながら、「スライダーが移動するたびに現在のスライドをリーダに読ませるとうるさい」という別の意見もあります(特に自動再生が有効な場合は顕著です)。Splide v3ではこの機能の導入を見送ることにしましたが、もし必要な場合は、以下のような拡張機能を作成することで実装できます。
export
function
LiveRegion
(
Splide
)
{
let
liveRegion
;
function
mount
(
)
{
liveRegion
=
document
.
createElement
(
'div'
)
;
liveRegion
.
setAttribute
(
'aria-live'
,
'polite'
)
;
liveRegion
.
setAttribute
(
'aria-atomic'
,
'true'
)
;
liveRegion
.
classList
.
add
(
'visually-hidden'
)
;
Splide
.
root
.
appendChild
(
liveRegion
)
;
Splide
.
on
(
'moved'
,
onMoved
)
;
}
function
onMoved
(
)
{
liveRegion
.
textContent
=
`
Slide
${
Splide
.
index
+
1
}
of
${
Splide
.
length
}
`
;
}
function
destroy
(
)
{
Splide
.
root
.
removeChild
(
liveRegion
)
;
}
return
{
mount
,
destroy
,
}
}
JavaScript
export function LiveRegion( Splide ) { let liveRegion; function mount() { liveRegion = document.createElement( 'div' ); liveRegion.setAttribute( 'aria-live', 'polite' ); liveRegion.setAttribute( 'aria-atomic', 'true' ); liveRegion.classList.add( 'visually-hidden' ); Splide.root.appendChild( liveRegion ); Splide.on( 'moved', onMoved ); } function onMoved() { liveRegion.textContent = `Slide ${ Splide.index + 1 } of ${ Splide.length }`; } function destroy() { Splide.root.removeChild( liveRegion ); } return { mount, destroy, } }
この拡張機能は主に、次のようなことをしています。
- ライブリージョンフィールドの作成
- スライドが移動したのち、フィールドのテキストを更新
しかしこのままではテキストが表示されてしまいますので、いわゆる「視覚的に隠す」方法を用いて、スクリーンリーダには検出可能、かつ人間には見えないようにする必要があります。
.visually-hidden
{
border
:
0
;
clip
:
rect
(
0
0
0
0
)
;
height
:
1
px
;
margin
:
-1
px
;
overflow
:
hidden
;
padding
:
0
;
position
:
absolute
;
width
:
1
px
;
}
CSS
.visually-hidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
Splideを初期化する際、この拡張機能を登録すれば実装完了です。
import
{
LiveRegion
}
from
'...'
;
new
Splide
(
'.splide'
)
.
mount
(
{
LiveRegion
}
)
;
JavaScript
import { LiveRegion } from '...'; new Splide( '.splide' ).mount( { LiveRegion } );
下のスライダーを用いれば、結果を確認できます。スクリーンリーダを起動し、スライダーを移動してみてください。移動が完了するたびに、リーダが現在のスライド番号を読み上げます。
- 01
- 02
- 03
- 04
- 05
- 06
- 07
- 08
- 09