実践Webアクセシビリティ

SPAのアクセシビリティ:ルーティング時のフォーカス管理の実装方法

Tags: SPA, アクセシビリティ, ルーティング, フォーカス管理, JavaScript

はじめに

シングルページアプリケーション(SPA)は、ページの再読み込みなしにコンテンツを動的に更新することで、リッチで滑らかなユーザー体験を提供します。しかし、この動的な挙動は、従来のマルチページアプリケーション(MPA)とは異なるアクセシビリティの課題を生じさせます。特に、新しいページコンテンツがレンダリングされた際に、キーボードフォーカスが適切に管理されないという問題が発生しがちです。

本記事では、SPAにおけるルーティング後のフォーカス管理の重要性を解説し、具体的な実装方法をコード例とともにご紹介します。経験3年程度のWebデベロッパーが、読んですぐに実践できるよう、ステップバイステップで進めていきます。

なぜルーティング時にフォーカス管理が必要か

MPAでは、ページ遷移が発生するとブラウザが自動的にページをリロードし、通常はドキュメントの先頭にフォーカスがリセットされます。これにより、キーボードやスクリーンリーダーを利用しているユーザーは、新しいページの開始地点からコンテンツを探索し始めることができます。

しかし、SPAではJavaScriptによってコンテンツが非同期で更新されるため、ブラウザによる自動的なフォーカスリセットは発生しません。その結果、ユーザーがルーティングによってページ遷移したつもりでも、フォーカスは前のページの最後の操作要素に残ったままになります。

この状態では、特にスクリーンリーダー利用者は新しいコンテンツが表示されたことに気づきにくく、キーボードユーザーはTabキーを押しても予期しない場所からナビゲーションが始まる可能性があります。これは、ユーザーが新しいページの内容をスムーズに把握し、操作することを大きく妨げます。

WCAG 2.1のガイドライン2.4.3 フォーカス順序や2.4.7 フォーカス可視にも関連するこの課題を解決するためには、JavaScriptを用いて明示的にフォーカスを管理する必要があります。

具体的な実装手順:ルーティング後のフォーカス移動

ルーティング成功時にフォーカスを移動させる基本的な考え方は以下の通りです。

  1. ルーティングが完了し、新しいページのコンテンツがDOMに反映されたことを検知します。
  2. 新しいページのコンテンツの開始地点(またはそれに準ずる場所)を特定します。
  3. 特定した要素にJavaScriptの focus() メソッドを用いてプログラム的にフォーカスを移動させます。
  4. 必要に応じて、ユーザーにページタイトル変更を伝えるために、ページのタイトル(<title>要素)を更新します。

ステップ1:ルーティング完了の検知

使用しているSPAフレームワーク(React Router, Vue Router, Angular Routerなど)やライブラリによって、ルーティング完了を検知する方法は異なります。一般的には、ルーターのイベントリスナーやフックを利用します。

例: Vanilla JavaScript + History API の場合

History APIを使用している場合は、popstate イベントや pushState/replaceState をラップしたカスタムイベントなどでルーティング完了を検知できます。

// History APIの変更を検知 (例)
window.addEventListener('popstate', function() {
  // URLが変更された
  handleRouteChange();
});

function handleRouteChange() {
  // 新しいページのコンテンツがレンダリングされた後に実行する必要がある
  // ここにフォーカス管理のロジックを記述
  console.log('Route changed to:', window.location.pathname);
  focusNewPageContent();
}

例: ライブラリ/フレームワークの場合

いずれの場合も、重要なのは「新しいコンテンツがDOMに完全にレンダリングされた」にフォーカス移動の処理を実行することです。非同期処理が必要な場合や、フレームワークのレンダリングサイクルを待つ必要がある場合があります。

ステップ2:フォーカス移動先の特定

フォーカスを移動させるべき最適な場所は、通常「新しいページのメインコンテンツの開始地点」です。これにより、ユーザーはページの導入部から内容を把握できます。一般的なフォーカス先候補は以下の通りです。

<main> 要素などのコンテンツコンテナにフォーカスを移動させるのが、実装としてシンプルで効果的です。

ステップ3:要素へのフォーカス移動

特定した要素に対して element.focus() メソッドを呼び出します。ただし、要素がキーボードフォーカス可能である必要があります。通常、リンク (<a>), ボタン (<button>), フォーム要素 (<input>, <select>, <textarea>) などはデフォルトでフォーカス可能です。しかし、<div><main> のような要素はデフォルトではフォーカスできません。

これらの要素をプログラム的にフォーカス可能にするためには、tabindex 属性を利用します。

ルーティング後のフォーカス移動においては、ユーザーがTabキーでその要素を巡回する必要はないため、tabindex="-1" を使用するのが一般的です。これにより、focus() メソッドでターゲット要素にフォーカスを移動させつつ、通常のキーボードナビゲーションフローを妨げません。

コード例: <main> 要素へのフォーカス移動

まず、新しいページのメインコンテンツを囲む要素に tabindex="-1" を設定します。

<main id="main-content" tabindex="-1">
  <h1>新しいページのタイトル</h1>
  <p>ここに新しいページのコンテンツが入ります。</p>
  <!-- 他のコンテンツ -->
</main>

次に、ルーティング完了を検知した後に、この要素を取得してフォーカスを移動させます。

function focusNewPageContent() {
  const mainContent = document.getElementById('main-content');
  if (mainContent) {
    mainContent.focus();

    // フォーカスが当たった要素がデフォルトでアウトライン表示されない場合、
    // CSSで視覚的なフォーカスインジケーターを提供することを忘れないでください。
    // 例: #main-content:focus { outline: 5px solid blue; }
  }
}

// ルーティング完了時に focusNewPageContent() を呼び出す
// (フレームワーク/ライブラリに応じた実装が必要です)
// 例:
// router.afterEach(() => {
//   // レンダリング完了を待つ必要があるかもしれません (requestAnimationFrameなど)
//   requestAnimationFrame(() => {
//     focusNewPageContent();
//   });
// });

requestAnimationFrame を使用することで、DOMの更新がブラウザによって描画されるのを待ってからフォーカス移動を実行できます。これにより、非同期レンダリングが完了する前にフォーカスしようとして失敗するリスクを減らせます。

ステップ4:ページタイトルの更新

視覚的なユーザーだけでなく、スクリーンリーダーユーザーにもページの変更を伝えるために、ドキュメントのタイトル(<title>要素の内容)を更新することは非常に重要です。スクリーンリーダーの多くは、ページロード時やタイトル変更時にタイトルを読み上げます。

function updatePageTitle(newTitle) {
  document.title = newTitle;
}

// ルーティング完了時に新しいページのタイトルで呼び出す
// 例:
// router.afterEach((to) => {
//   updatePageTitle(to.meta.title || 'デフォルトタイトル');
//   requestAnimationFrame(() => {
//     focusNewPageContent();
//   });
// });

多くのSPAフレームワークのルーターは、ルート設定にメタ情報(例: meta: { title: 'ページタイトル' })を持たせ、それを活用してタイトルを自動更新する機能を提供しています。フレームワークの機能を確認し、活用することをお勧めします。

実装時の注意点とよくある落とし穴

テスト方法

実装したフォーカス管理が正しく機能しているかを確認することは非常に重要です。

まとめ

SPAにおけるルーティング時のフォーカス管理は、キーボードユーザーやスクリーンリーダーユーザーがサイトをスムーズに利用するために不可欠なアクセシビリティ対応です。ルーティング完了時に、メインコンテンツの開始地点など適切な要素に tabindex="-1"focus() を組み合わせてフォーカスを移動させ、同時にドキュメントタイトルを更新することで、多くのユーザーのナビゲーション体験を向上させることができます。

使用しているフレームワークやライブラリのルーティングシステムに合わせて、適切なイベント検知とDOM操作を実装してください。そして、必ずキーボード操作とスクリーンリーダーによるテストを実施し、実装が意図通りに機能していることを確認してください。

これはSPAのアクセシビリティ対応のほんの一歩ですが、ユーザーがサイト内で迷子になるのを防ぐための非常に効果的な手段です。ぜひご自身のプロジェクトに適用してみてください。