トピックを検索
Tabで近づくとフッターが遠ざかる無限スクロール
ショップのマグの一覧は、終わりが見えてくるたびに、フッターの手前へ12個ずつ追加されます。読み込み中の表示もただのdivのため、Tabキーでフッターに近づくと、そのつど新しいマグに行き当たり、スクリーンリーダーには追加されたことが伝わりません。
observer.observe(sentinel); 問題
スクロールで読み込み続ける一覧では、キーボードの利用者がその下のリンクに届かないか、読み込みのたびに今の位置を見失う。スクリーンリーダーの利用者には、項目が追加されたことが伝わらない
あるショップのカテゴリーページには48個のマグがあり、12個ずつ表示されます。ページ番号のリンクもボタンもありません。一覧の終わりが見えてくると、最後の行の下に「マグを読み込んでいます…」と表示され、少しすると12個のマグが加わって、その表示は消えます。マウスや指でスクロールしている限り、一覧はどこまでも続き、それがこのデザインのねらいです。一覧の下のフッターには、「お問い合わせ」と「返品」のリンクがあります。
マウスを使わずに、Tabキーで「お問い合わせ」へ向かってみます。フォーカスはマグからマグへ移ります。最後の数行に来るたびに、フォーカスのあるマグを見せるためにページがスクロールし、一覧の終わりが近づいて、フォーカスとフッターの間に12個のマグが追加されます。次にTabキーを押すと、その新しいマグに入ります。「お問い合わせ」に届くのは、48個すべてが読み込まれ、Tabキーでそのすべてを通り過ぎた後です。終わりのない一覧では、いつまでも届きません。その間、スクリーンリーダーは何も言いません。「マグを読み込んでいます…」はただの div に出ては消え、新しいマグは音もなく加わります。
もう1つの版では、追加のたびに list.innerHTML += html を使っています。この1行は一覧のマグをすべて作り直すため、フォーカスのあったマグもほかのマグと一緒に消え、フォーカスはページそのものに移ります。Chromeでは、次にTabキーを押すと最初のマグに戻ります。12個読み込むたびに、キーボードの利用者は一覧の先頭へ戻され、スクリーンリーダーの利用者は読んでいた位置を見失います。
達成基準2.1.1「キーボード」は、ページでできることをすべてキーボードでもできることを求めています。「お問い合わせ」と「返品」は普通のリンクですが、Tabキーで届くのは、読み込むものがなくなったときだけです。フォーカスが近づくたびに、その手前で一覧が伸びるからです。このページでは48個すべてのマグを通る必要があり、終わりのない一覧では、フッターのリンクにキーボードからはまったく届きません。フッターは最初からページにあるため、スクリーンリーダーならランドマークのキーで直接移動できますが、Tabキーにはそうしたキーがありません。
達成基準2.4.3「フォーカス順序」は、ページの意味と操作性を保つ順序でフォーカスが移ることを求めています。一覧が作り直されると、フォーカスのあったマグはもう存在せず、ブラウザーはフォーカスをページそのものに置くしかありません。次にTabキーを押したときの移動先は、ページではなくブラウザーが決めます。Chromeでは最初のマグです。マグの並びは変わっていなくても、読み込むたびに、その中で自分がどこにいたかが分からなくなります。
達成基準4.1.3「ステータスメッセージ」は、さらに読み込むのを待っているといったページの状態を知らせるメッセージが、フォーカスを受けずにスクリーンリーダーへ届くことを求めています。「マグを読み込んでいます…」はそうしたメッセージですが、役割のないただの div に表示されるため、スクリーンリーダーには伝わりません。これは失敗例F103にあたります。マグそのものはステータスメッセージではなくコンテンツで、4.1.3は1つずつ読み上げることまでは求めていません。一方、表示中の件数をページに出す場合、その件数はステータスメッセージです。
無限スクロールは、ショップのカテゴリーページや検索結果、ニュースサイト、SNSのフィードでよく見られ、スマートフォン向けのレイアウトでは特に多く使われます。1つの共通の一覧部品から作られていることが多いため、同じ問題がサイト内のすべての一覧で繰り返されます。遠ざかるフッターには、問い合わせ、配送、返品、アクセシビリティ方針といった、困ったときに探すリンクが置かれていることがよくあります。
10グループ中2グループに影響
どのマグも名前のあるリンクで、フッターも footer 要素です。マークアップとして不正な箇所はないため、axeが報告することはありません。フッターに届くか、読み込みの後もフォーカスが残るかは、ページを実際に操作して初めて分かります。
EaseWebの診断ツールは、ページを最後までスクロールしてフッターより前のリンクを数え、Tabキーで進んだときのように、その最後のリンクにフォーカスを置きます。自動で伸びる一覧は、人が確認する項目として渡します。一覧がすぐに終わるか、一覧を飛ばすキーがあるかは、人にしか判断できないからです。読み込みでフォーカスのあるリンクが消え、フォーカスがページに残る場合は、コードだけで証明できるため、2.4.3の問題として報告します。ライブリージョンがないページに読み込み中の表示があれば、それも人が確認する項目として渡します。読み上げられる内容が聞く人にとって筋が通っているかは、どの確認でも分かりません。
AIによる修正は、ここでARIAに頼りがちです。一覧全体に aria-live="polite" を付けると、スクリーンリーダーは読み込むたびに12個のマグの名前をすべて読み上げます。伝わるのは件数ではなく、雑音です。role="feed" を付けるだけでは、Tabキーを使う人には何も変わりません。フィードを飛ばすキーは、ページのスクリプトが用意するものだからです。
試してみる
キーボードだけで、マグの一覧の下にある「お問い合わせ」まで移動してみましょう
問題1: Tabキーでマグを順に移動します。フォーカスが終わりに近づくたびに、フッターの手前に12個のマグが追加され、下の欄のとおり、スクリーンリーダーには何も聞こえません。
問題2: 同じ一覧を、読み込むたびに作り直します。フォーカスはそれまでのマグから外れ、次にTabキーを押すと最初のマグから始まります。
修正を適用します。 ボタンの場合は、Tabキーでボタンの先の「お問い合わせ」へ進むか、ボタンを押してさらに読み込みます。フィードの場合は、どのマグからでもCtrl + Endを押します。
問題あり 修正できますか?
スクリーンリーダーの読み上げ:
(Tabキーでフッターへ向かう)
修正方法
利用者が求めたときに読み込み、一覧を作り直さずに新しい項目を追加し、表示中の件数をステータスの行で伝える
- 1一覧の後ろにあるものへキーボードで届くようにする。一覧の後ろのボタンで読み込むか、フィードに一覧を飛ばすキーを用意する。
- 2新しい項目は表示中の項目の後ろに追加し、フォーカスのある一覧を作り直さない。
- 3読み込み中のメッセージと新しい件数は、最初からページにあるステータスの行に入れる。
スクロールで読み込むマグの一覧
<main> <h1>マグ</h1> <ul class="products" data-total="48"> <li> <a href="/mugs/enamel-white"> <img src="/images/enamel-white.jpg" alt=""> ホーローのマグ(白) </a> </li> … </ul> <div class="loading" hidden>マグを読み込んでいます…</div> <div class="sentinel"></div></main><footer> <a href="/contact">お問い合わせ</a> <a href="/returns">返品</a></footer>「さらに表示」ボタンとステータスの行
<main> <h1>マグ</h1> <ul class="products" data-total="48"> <li> <a href="/mugs/enamel-white"> <img src="/images/enamel-white.jpg" alt=""> ホーローのマグ(白) </a> </li> … </ul> <p class="count" role="status">48個中12個を表示</p> <button type="button" class="load-more">さらに12個表示</button></main><footer> <a href="/contact">お問い合わせ</a> <a href="/returns">返品</a></footer>この方法を選ぶ理由:ページは、利用者が求めたときだけ変える
一覧の後ろにある空の div を監視し、ウィンドウから200ピクセル以内に入ったところで、次の12個のマグを取得してフッターの手前に追加します。最後の数個のマグにフォーカスを置くと、ページがそこまでスクロールして読み込みが始まるため、Tabキーはフッターの前で必ず新しいマグに行き当たります。読み込み中の表示はただの div で出し入れされ、スクリーンリーダーには伝わりません。
ボタンが押されるまで何も読み込まないため、フッターは動かず、最後のマグ、ボタンと進めば、次のTabキーで届きます。HTML標準のボタンなので、キー操作の処理を書かなくても、EnterキーとSpaceキーで押せます。新しいマグは今あるマグの後ろに追加されるため、フォーカスのあった要素は消えず、フォーカスは最初の新しいマグへ移り、利用者はそこから続きを読めます。ステータスの行は最初からページにあるため、「マグを読み込んでいます…」、続いて「48個中24個を表示」と書き込まれるたびに、スクリーンリーダーはフォーカスを移さずに読み上げます。この行は、内容を変えるボタンのすぐそば、利用者が目を向ける場所に置いています。すべてのマグを表示すると、ボタンはなくなり、行は「48個中48個を表示」になります。
どちらも基準を満たしますが、「さらに表示」ボタンを使ってください。HTML標準のボタンと数行のスクリプトですべてを満たし、マウス、キーボード、スクリーンリーダー、音声操作のどれでも同じように動きます。マグをいつ増やすかは利用者が決めるため、フッターには常にボタンの次のTabキーで届きます。フィードなら無限スクロールを残せますが、利用者が覚えなければならないキーと、対応にばらつきのあるパターンを抱えることになります。そのため、それだけでは不十分としています。デザイン上どうしてもスクロールで読み込む必要があるときにだけ選び、訪問者が使うスクリーンリーダーでテストしてください。ページ番号のリンクも基準を満たし、戻ったり、ブックマークしたり、共有したりできる結果のページは、多くの場合さらに使いやすくなります。行き先を伝えるページ番号の作り方は、現在のページも行き先も伝えないページネーションで扱っています。
フレームワークでも、innerHTML += と同じようにフォーカスが落ちることがあります。項目に安定したキーがない一覧や、読み込むたびに新しい一覧に差し替える一覧は、一から作り直され、フォーカスのある項目も一緒に消えます。各項目のキーには、商品IDのような、その項目に固有の値を使い、次のページは同じ一覧の最後に追加します。
「さらに表示」の後は、ボタンではなく最初の新しいマグへフォーカスを移します。ボタンに残すと、新しいマグはフォーカスより前に来るため、次のTabキーはそのすべてを飛ばしてフッターへ進み、利用者は自分で読み込んだマグを探しに戻らなければなりません。件数はフォーカスを移した後に書き込みます。こうすると、まず新しいマグの名前が読み上げられ、politeの通知である件数はその後に続きます。
読み込みが遅いときや失敗したときも、伝える必要があります。同じステータスの行に「マグを読み込めませんでした。もう一度お試しください。」と書き、ボタンはそのまま残します。読み込み中に何を、いつ伝えるかは、読み上げられない読み込み中の表示で扱っています。絞り込みや並べ替えで一覧が入れ替わるときは、結果の件数を伝えます。件数が読み上げられない検索・絞り込み結果を参照してください。
ボタンを最初は /mugs?page=2 のような次のページへのリンクにしておき、スクリプトでボタンに変えることもできます。こうすると、スクリプトの読み込み前や失敗したときにも動きます。表示中のマグの数を history.replaceState() でアドレスに書いておくと、「戻る」や共有したリンクで一覧の同じ位置に戻れます。
最初の1、2回はスクロールで読み込み、その後に「さらに表示」ボタンを出す一覧もあります。ボタンが決まった回数の読み込みの後に出て、フッターまでに通るマグの数が決まっている限り、基準を満たします。全体の数が分からないフィードは aria-setsize="-1" とします。新しい記事を読み込むときに古い記事を取り除くフィードは、すべての記事の aria-posinset を正しく保ち、フォーカスのある記事を取り除かないようにします。Macのキーボードの多くには、End、Page Up、Page Downのキーがなく、Fnキーと矢印キーで代わりに入力します。Macでもフィードのキーをテストしてください。
デザインの案
リストの終わりの見せ方は一つではない
ガイドのマグの一覧を、フッターに届くまま終える方法をさらに3通りと、届かなくなる流行りの形を2通り示します。Tabキーで順に移動し、ボタンを押してみてください。
デザインファイルで
- 最後の行の後、フッターの前に来るものを、ボタンかページのリンクとして描く。勝手に増える行にはしない。
- 表示中の数を「48個中24個を表示」のように言葉でボタンのそばに書き、変わったときに読み上げる行だと書き添える。
- 読み込み中のボタンも、ラベルとフォーカスの枠を付けたまま、同じ場所に描く。
- 読み込んだ後にフォーカスが移る先として、最初の新しい項目に印を付ける。
合格
- 薄く見える次の行と、その上のボタン 次の行が白いもやの向こうに透け、その上に輪郭線のピル型ボタンが載るため、続きがあることが目で分かります。ボタンを押すまで何も増えないので、フッターはボタンの次のTabキーで届きます。薄いマグは絵で、リンクではありません。
- 進み具合の線と、輪郭線のピル型ボタン 言葉の「48個中24個」の下に細いバーがあり、その下に輪郭線のピル型ボタンがあります。言葉がステータスの行で、変わると読み上げられます。バーは同じことを繰り返すだけなので、デザインに合わせて控えめにできます。
- コンパクトなページ切り替え 1本の角丸のバーに、「4ページ中2ページ目」を挟んで「前へ」と「次へ」を置きます。どのページも独立したページなので、利用者の足元で何かが増えることはなく、フッターは動かず、ブックマークもできます。矢印には言葉の名前が必要です。
不合格
- 勝手に読み込まれるプレースホルダーのカードWCAG 2.1.1 最後のマグへTabキーで移動すると、その下に灰色のプレースホルダーのカードが勝手に現れ、マグに変わります。滑らかに見えますが、Tabキーで終わりに近づくたびに、フッターの手前に新しい行が届きます。プレースホルダーは残し、ボタンを押した後に出します。
- スピナーに変わるピル型ボタンWCAG 2.4.3 押してみてください。読み込みの間、ピル型ボタンがスピナーに置き換わるため、フォーカスのあったボタンが消え、フォーカスも一緒に失われます。ボタンが戻っても、フォーカスはどのコントロールにもありません。ボタンはその場に残し、スピナーはボタンの中に出します。
修正の確認
5項目、マウス不要
確認方法: キーボード、スクリーンリーダー
OS、ブラウザーと支援技術のバージョン、ビルド、実施日、各手順の実際の結果を記録してください。一覧、その部品、ライブラリーを変更した際は、再度確認します。
スクリーンリーダーでの role="status"、role="feed"、aria-posinset, aria-setsize、aria-busy="true"の読まれ方
制限
このサンプルは、どの一覧でも同じ結果になることを保証するものではありません。スクリプトは最小限の例で、エラー、絞り込み、並べ替え、ブラウザーの「戻る」ボタンを扱っていません。画面外にスクロールした行を取り除く仮想化ライブラリーで描く一覧には、別の確認が必要です。リンクと「かごに入れる」ボタンのように、1枚の商品カードに複数のコントロールがあると、一覧を通り過ぎるための操作が倍以上になり、「さらに表示」ボタンはいっそう重要になります。ページの中のスクロール領域がスクロールで読み込み続ける場合の届き方は、キーボードでスクロールできないスクロール領域で扱っています。訪問者が使う支援技術で、一連の操作全体をテストしてください。
よくある問題を再現した学習用のサンプルです。実在のクライアントの診断結果ではありません。コードは出発点となる実装例のため、実際の製品でも確認してください。ここでは支援技術でのテスト結果は報告していません。
コードを更新しても、使いやすさを保つために
ガイドの内容を、コーディングエージェントやCIで使える開発ルールにまとめます。更新後は、キーボードとスクリーンリーダーで再テストし、問題が再発していないか確認します。