画面遷移から考えるNuxtアプリケーションをアクセシブルにする方法

スライドタイトル:画面遷移から考えるNuxtアプリケーションをアクセシブルにする方法

翻訳記事一覧

日本語ページ / English page

はじめに

はじめに自己紹介です。

写真:やまのく

やまのくと申します。

株式会社クラウドワークスにて、フロントエンドエンジニアとして施策の開発やデザインシステムの開発にも携わっておりました。現在は技術的負債解消をリードするチームに所属し、レガシーフロントエンド環境の刷新に関わっております。

ウェブの仕様についてやウェブアクセシビリティにまつわることに興味があります。

また、一児の父でゴールデン・レトリバーと3匹の猫の飼い主をやっております。

本題に入る前に今回のテーマに関わるウェブアクセシビリティとはなにかを説明します。

アクセシビリティとはアクセスと能力の単語を組み合わせたもので、情報や製品、サービスが利用できるようにする意味で使われる言葉です。略してA11yとも書かれていることがあります。

Access + Ability = Accessibility (A11y)

ウェブアクセシビリティはウェブの文脈にて考えられるアクセシビリティのことで「利用者の障害の有無やその程度、年齢や利用環境にかかわらず、ウェブで提供されている情報やサービスを利用できること、またはその到達度」とされています。

ウェブアクセシビリティについてお話しする際に、最初に理解しておくべき重要なポイントがあります。それは、「元々ウェブはアクセシブルなもの」ということです。

ここで、コロナ禍で多くの人々がリモート勤務を余儀なくされた時期を思い出してみてください。その時、私たちはオンラインで情報を得たり、発信したりすることが可能でした。

これは、ウェブという媒体を通じて物事がアクセシブルになっている一例です。

そんなウェブのおかげで障害当事者もウェブサービスを通じることでさまざまなものにアクセスできるようになっています。

これは視覚障害者向けの支援技術の利用状況に関する調査によるもので、彼らがパソコンやスマートフォンを介してさまざまなサービスを利用していることが明らかになっています。

ウェブアクセシビリティを確保することは、障害を持つ人々に対しても公平なサービスを提供するための手段であり、これにより様々な背景を持つ人々がサービスを利用できるようになります。

今回Vue Fes Japan 2023ということで日本とウェブアクセシビリティに関するタイムリーな話題についてを紹介します。

それは改正障害者差別解消法環境の整備についてです。

障害者差別解消法は障害を理由とする差別の解消と、障害のある人もない人も、互いにその人らしさを認め合いながら共に生きる社会を実現することを目的とする法律です。

この法律により、障害がある人々を不公平に差別するのではなく、彼らが日常生活を送る上で直面する困難を和らげたり、解決したりするための措置や配慮が求められます。

来年の4月からは、障害のある人々への合理的な配慮を提供することが、民間企業においても努力義務ではなく法的な義務化になります。

合理的配慮とは、障害を持つ人が社会に参加する際に必要な特別な支援や調整を指します。これにはさまざまな形があります。

例として、障害を持つ人が筆記が難しい場合に代筆をしてもらう、コミュニケーションを取る際に、絵カードや写真カードやタブレット端末を使用してみる、肢体不自由な人が移動する際には、移動のサポートを提供することも合理的配慮の一環と言えます。

合理的配慮は、障害を持つ人が困難を感じた際、その人の個別のニーズに応じて無理のない範囲で対応していく必要があります。

ですが、合理的配慮が必要な人との関係が長期にわたる場合、毎回対応をすることが双方にとって負担となることがあります。この負担を減らすため、効率的かつ持続可能なサポート方法を検討することが欠かせません。

事前に改善措置を施しておくことで、さまざまな場面で障害を持つ個々の人々への合理的配慮をスムーズに行えるようになるのですが、これを環境の整備と言います。

例えば、聴覚障害者と筆談でコミュニケーションを取る必要がある状況では、タブレットなどの入力装置を準備しておくことで、その人自身が入力できるようにし、都度の対応や外部からの支援を必要としなくなります。

ウェブアクセシビリティは、合理的配慮そのものではなく、事前的な措置として行う環境の整備で行うためのものとしての位置づけです。

先ほどの合理的配慮で説明したように、これまで電話や筆談でしか対応できなかったことも、ウェブを介して行うことで非同期的に、個々人が自分で問い合わせや入力を行えるようになります。

ウェブアクセシビリティを実現することで、利用者の満足度が向上し、対応や問い合わせが減少することで全体の業務効率が向上することが期待されています。

そんなウェブアクセシビリティの重要性を理解したうえで立ち返ってほしいことがあります。

それは私たちがこれまでに作ってきた、そして現在作っているVue.jsやNuxt.jsを使用したアプリケーションが実際にアクセシブルであるかどうか、ということです。

前回のVue Fes Japanでは「Vue.jsでアクセシブルなコンポーネントをつくるために」をテーマに、誤ったアクセシビリティ対応についてや、Vue.jsの強みを活かしたアクセシビリティへの配慮とテストについて話させていただきました。

その際WebAIMという団体によるクライアントサイドフレームワークのアクセシビリティの対応状況を取り上げました。

The WebAIM Million 2023のJavaScriptフレームワークの結果。Vue.jsでのアクセシビリティ対応は平均より20.9%も低い結果が出ている

数字は改善傾向にありますが、2023年時点でも結果がまだ悪いことが私個人としては気になっています。

私を含むVue.jsやNuxt.jsを用いる開発者たちが、ウェブアクセシビリティを正確に考慮した開発を継続できるよう、今回の発表でもその点を話すことができればと考えています。

今回は「画面遷移」におけるアクセシビリティに焦点を当てて話を進めていきたいと思います。

改めてよろしくお願いいたします。

クライアントサイドルーティングという手法

まず初めに、クライアントサイドルーティングという手法について説明しましょう。

通常の画面遷移では、サーバーサイドが新しいページのHTMLを生成し、それをクライアントに送信して画面を表示します。これが一般的なルーティングの形になります。一方で、クライアントサイドルーティングは全ての処理をクライアント側で完結させる手法です。

この手法は、シングルページアプリケーションや画面内の一部を動的に更新する際に利用されます。

クライアントサイドルーティングにはいくつかの利点があります。

これらの特長は、ウェブサイトやアプリケーションのユーザー体験を向上させる上で大いに役立ちます。

ページ間のトランジションアニメーションといえば、最近さまざまなクライアントフレームワークで導入されつつあるView Transitions APIがあります。Nuxt.jsではバージョン3.4からexperimentalで導入されています。

現在はView Trainsitions API非対応ブラウザでのフォールバックがないため、そのまま使用する場合は対応するブラウザのみでしか動かないことに注意です。

実際のデモを見てみましょう。

画面遷移するごとにクリックした対象部分が拡大されているような効果が入っています。

異なる2要素間にトランジション効果をつけることができるため、ページ全体やコンポーネント単一でのトランジションはできたものから更に一段階上の画面遷移を実現できるようになります。

スムーズな画面遷移を実現するための手法も増えて、導入障壁も下がりつつあるため、シングルページアプリケーションが出始めた当初よりクライアントサイドルーティングはより身近なものとなってきたと思います。

画面遷移におけるアクセシビリティの問題点

より便利に身近になってきたクライアントサイドルーティングですが、それによって見逃してしまうアクセシビリティの問題は存在します。次は問題点についてと、その問題の解決策についてを紹介していきます。

何が変化したかが支援技術に伝わらない

まず1つに何が変化したかが支援技術に伝わらないというのがあります。これは画面遷移のアクセシビリティを考慮する際に最も気付きづらい問題です。

支援技術とは、障害者がウェブを利用、操作する際に補助として使われるハードウェアやソフトウェアの総称です。

クライアントサイドルーティングを用いた画面遷移は、視覚的にはどの部分が変わったのか明確に捉えることができるかもしれません。しかし、支援技術を利用してページを閲覧しているユーザーはその変化に気づきにくい可能性があります。

画面内のものを読み上げてくれる支援技術のスクリーンリーダーを使用してクライアントサイドルーティングの様子を見てみましょう。

このように画面上では適切にページが切り替わっているものの、スクリーンリーダーを使用しているユーザーにはその変化が通知されないことが分かるかと思います。

このような問題はどのように回避すればよいのでしょうか。

解決する手段の1つとしてWebコンテンツをよりアクセシブルにするための技術仕様であるWAI-ARIAを活用したARIAライブリージョンというものがあります。ARIAライブリージョンは、動的なコンテンツ変更をユーザーに通知するために使用されます。

画面遷移時にユーザーに通知を行うアクセシブルな方法として、ARIAライブリージョンを利用して title 要素の変更を通知する手法があります。

具体的な手法を説明していきます。

まずはページ単位で useHead からそのページの title を指定します。

useHead({
  title: 'About Page',
});

次にタイトル要素を取得してきて、ページが移動する度に変更を検知させます。

const route = useRoute();
const message = ref<string>("");

const announceRoute = () => {
  const title = document.title;
  message.value = title;
};

onMounted(() => {
  announceRoute();
});

watch(
  () => route.fullPath,
  () => {
    setTimeout(() => {
      announceRoute()
    }, 300);
  }
);

今回の例では Route のパスを検知した上で変更させています。setTimeout を使用して少し遅延させることで、支援技術によっては直ちに反映されない通知の取得タイミングのズレを補正しています。

そして通知させるための変数をHTML側に組み込みます。

<p
  role="status"
  aria-live="assertive"
  class="visually-hidden"
>
  {{ message }}
</p>

aria-live というWAI-ARIAを使用することで支援技術に通知させるようにできています。値は assertive を使用することで変更が即座にアナウンスされ、ユーザーの注意を引くことができます。

対応するHTML要素はスクリーンリーダーが検知する目的のみに使用されるため、通常は画面上では非表示になるようにします。

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

ここで注意することとしては display: none で非表示にすると支援技術では存在を検知できないため視覚的にだけ見えないようにする必要があります。このスタイル手法は通称Visually Hiddenと呼ばれているものです。

それでは変更したものを一緒に見てみましょう。

このようにしてリンクが押された場合に画面が切り替わっていることを通知できるようになりました。

Nuxt.jsで作られたマストドンクライアントの「elk(エルク)」ではこの手法をコンポーネントとして活用しています。

支援技術を利用してページを確認する際には、ページが読み込まれた後に現在のページタイトルがアナウンスされるのを聞くことができます。

ソースコードは公開されているので、興味がある方は実際のアプリケーションがどのように動作するのか、チェックしてみてください。

フォーカスマネジメント

次に画面が切り替わったことでのキーボード操作についてです。

キーボード操作では、エンターキーまたはスペースキーで項目を決定したり、矢印キーで項目を移動したり、エスケープキーで操作を解除するなどの操作があります。

今回は、その中でも特にタブキーを使用したフォーカス移動に関する問題点をデモを通じてご紹介します。

ページ下部に位置するリンクを選択した際には、確かに画面のコンテンツは更新されますが、フォーカスの移動すると、以前の位置、すなわち下部のリンクからの移動となってしまいます。

状況によってこれが問題とならないこともありますが、今回のデモで示すような状況ではこの挙動が非常に不都合であることがはっきりと分かります。

こうした問題を解消するため、次に移動してほしい要素へフォーカスするための処理を挟む必要があります。

Vue.jsでの対象の要素を取得する場合、ref を使用しテンプレート参照します。

const focusedRef = ref<HTMLElement>();

タブキーを使用したフォーカス移動の際には、a 要素や button 要素のようなものはデフォルトでフォーカスを受け入れることができますが、フォーカスできないものもあります。

この問題を解決するためには、tabindex 属性を使用することで、要素へフォーカス可能になるよう設定できます。

onMounted(() => {
  if (focusedRef.value) {
    focusedRef.value.tabIndex = -1;
    focusedRef.value.focus();
  }
};

この値を -1 に設定すると、要素は通常のキーボード操作ではフォーカスされなくなり、誤って操作されるリスクを防ぐことができます。その後、直接対象の要素をフォーカスさせています。

次に考えるべき点は、どの位置にフォーカスを当てるか、ということです。

ページを移動した際、最初にフォーカスを当てるべき位置としては、ページの主要なコンテンツを包含する main 要素が適していると考えられます。しかし、この方法には問題点があります。

具体的な例を見てみましょう。ここでは、アバウトページへ遷移した際に main 要素にフォーカスを当てるような動作を紹介します。

気付きづらいかもしれませんが、main 要素にフォーカスが当たると、内包する全ての内容を一度に読み上げてしまいます。

今回は main 要素内のものが少ないため読み上げるものはわずかでしたが、実際の場合はこれよりも多いコンテンツがあることが想定されます。

これはWindowsで利用できるNVDAというスクリーンリーダーでの結果です。一方でmacOSのスクリーンリーダーではこのような読み上げ方をしませんが、デバイスやOSによってスクリーンリーダーの挙動は異なるため、注意が必要です。

では、この問題を解決するためにはどうしたらよいのでしょうか。

対象が大きすぎて全体を一度に読み上げてしまうのであれば、フォーカス対象の要素を小さくしてみるのが一つの方法です。

ページ内に見出し要素が存在する場合は、その要素にフォーカスを移動させるように変更することをお勧めします。

もし見出しがない場合は、スキップリンクを用意して、そこへフォーカスを移動させ、ARIAライブリージョンでページタイトルを読み上げるのも1つの方法だと思っています。

スキップリンクとは、主にキーボードを使用してブラウジングするユーザーのための機能で、ページの先頭から直接メインコンテンツの開始部分へジャンプできるページ内リンクのことを指します。

この手法自体はウェブアクセシビリティで必須のものではありませんが、スキップリンク自体があれば使う人もいるため一定効果があるものだと考えられています。

GitHubでもこの方法が採用されており、ページの先頭からメインコンテンツの直前の要素に簡単に移動することができ、そこから続けてメインコンテンツにアクセスすることが可能です。

スクリーンショット:Nuxt.jsのリポジトリページで「Skip to content」というリンクが表示されている

スキップリンクの作成方法に関しては、Vue.js公式ドキュメントのアクセシビリティページでも詳しく説明されていますので、是非ご覧ください。

以上の内容からページ遷移した後にフォーカスする対象としては、情報量が少ない箇所を選ぶことが望ましいと言えます。

フォーカスの開始位置において必ずここにするという明確なルールは存在しません。状況によっては、フォーカス位置を変更しない方がユーザーにとって理解しやすい場合もあるでしょう。

しかし、その際にはユーザーには「位置が変わらない」という挙動を学習する必要があります。そのため、サーバーサイドでのページ遷移と同様、ブラウザの上部から遷移が始まるような形を基本としておくことが重要です。

過度なアニメーションで閲覧が阻害される

最後に過度なアニメーションで閲覧が阻害されるというのがあります。

ページトランジションアニメーションを使用することは、画面遷移の流れをスムーズにしてユーザーへの没入感を高めることができます。

一方で、全てのユーザーにとって最適な解決策であるとは限りません。特に前庭障害や認知障害を持つユーザーにとっては、アニメーションが原因で吐き気を催したり、混乱を引き起こしたりするリスクがあります。

現在のPCやスマートフォンの多くでは、OSレベルで「視差効果を減らす」設定を提供しています。

この設定は、画面上の動く視差効果を抑制し、より穏やかな表示に変更するものです。特にスマートフォンユーザーの中には、バッテリーの持ちを延ばすためにこの設定をONにしている方もいらっしゃるかもしれません。

視差効果を減らすための設定をフロントエンド側で制御する際には、prefers-reduced-motion を使用できます。

JavaScriptとWeb Animations APIを使用してアニメーション設定しているのであれば、window オブジェクトの matchMedia メソッドを使ってユーザーの設定を取得し、change イベントリスナーを使ってアニメーションを制御することが可能です。

const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
mediaQuery.addEventListener('change', () => {
  console.log(mediaQuery.media, mediaQuery.matches);
  // JavaScriptでのアニメーションを止める
});

ただし、この方法ではユーザーが設定を変更した時点で内容を反映させる場合、ブラウザをリロードする必要があります。

Vue.jsでComposition APIを使用している場合、VueUseという状態管理のユーティリティライブラリが使えます。この中に視差効果に関連するコンポジション関数が含まれています。

このようなツールを利用することで、よりスムーズにユーザーの設定に合わせた表示切り替えが実現できます。

もしCSSを使用して手早く設定を反映させたい場合、全ての要素に対して animation プロパティやそれに準ずるものを無効にすることで対応可能です。

@media (prefers-reduced-motion: reduce) {
  *,
  ::before,
  ::after {
    animation-delay: -1ms !important;
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    background-attachment: initial !important;
    scroll-behavior: auto !important;
    transition-duration: 0s !important;
    transition-delay: 0s !important;
  }
}

また、View Transitions APIを使用している場合も、animation プロパティを無効に指定することでアニメーションを停止させることができます。

@media (prefers-reduced-motion) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

アニメーションはCSSの設定を変更することで簡単に無効化することが可能です。

しかし、それによって実際に違和感なく動作するかどうか、実際に確認することが必要です。 視差効果を無効化する場合だけでなく、他のケースでも同様です。

コード上での変更を完了しただけで満足するのではなく、変更が意図通りに機能しているかを必ず確認してください。

以上がNuxt.jsにおける画面遷移についてのアクセシビリティについてでした。

画面遷移をWeb APIから解決する

次に、Routerライブラリやフレームワークに依存しない、将来のクライアントサイドルーティングでの開発に活用できるWeb APIについて紹介していきます。

画面遷移に関連するWeb APIとして、多くの方がHistory APIについてを思い浮かべるでしょう。

かつて「Pjax」と呼ばれるpushStateとAjaxを組み合わせたページ遷移に関する技術が利用されていました。ここで言及されるpushStateは、History APIのメソッドの一つです。

Vue Routerの場合、このHistory APIを模した動作を提供しており、pushreplacegoなどのメソッドが存在します。

ただし、History APIをそのまま使用するにはいくつか実装上の問題が発生します。

これらの問題を回避し、クライアントサイドルーティングを効果的に実現するため、Vue Routerのような各種Routerライブラリが開発されました。これらのライブラリによって、History APIは隠蔽された形で利用されています。

Routerによりブラウザに画面遷移を通知することは可能ですが、その通知をどのタイミングで行うべきかの情報はブラウザから提供されていません。これが意味するのは、わざと通知のタイミングをずらすような実装、例えば setTimeout を使用するようなもの、が必要になってしまいます。

そうした問題を解決するために新たに策定されているものがNavigation APIです。

Navigation APIはHistory APIでは解決できなかったクライアントサイドルーティングの問題点を解消してくれます。

このAPIを利用することで、画面遷移の開始と終了のタイミングを通知することができ、インターセプトを利用してスクロール位置の復元やフォーカス位置の調整なども行うことが可能になります。

特に、ページを進めるだけでなく、戻るボタンを使用した際やページのリロード時など、様々なシチュエーションで役立ちそうな機能があります。これにより、アクセシビリティを担保したユーザー体験の向上が期待できます。

2023年10月現在、Chromeでは実装されており動作するようになっていますが、SafariやFirefoxではまだ実装されていません。

スクリーンショット:Can I UseでのNavigation APIにまつわるブラウザ対応表

しかし、ウェブの相互運用性を向上させることを目的としたプロジェクト「Interop」において、各モダンブラウザで使用可能となるよう注力されているAPIの一つとして投票されています

過去に注力されたAPIの例としては、Container Queries、Inert属性、スクロールに関するCSSプロパティなどがありました。

こちらの対応状況については今のところ予測が難しい部分もありますが、各モダンブラウザにおいて取り入れられるのはそう遠くはなさそうだと期待されています。

おわりに

いかがだったでしょうか。

今回のテーマが、これまで知らなかった方々にとって新たな学びとなり、役立つ情報が得られたならば、それは大変嬉しく思います。

お話しした内容が、皆様にとって知らなかったことも多いかもしれません。そのため、少し複雑に感じる部分があるかもしれませんが、ご一緒に考えていただければ幸いです。

最初にも触れましたが、ウェブサイトは元々アクセシブルな作りとなっています。しかし、近年のフロントエンド開発の進化によってクライアントサイドルーティングが主流となり、以前は問題視されなかった多くのアクセシビリティの問題が表面化してきています。

サーバーサイドルーティングでは問題とならなかったアクセシビリティの課題が、クライアントサイドルーティングの導入により顕在化しているとも言えるでしょう。

誤解を恐れずに言うとクライアントサイドルーティングと言う選択肢には、既存のウェブの利点をあえて捨て去り、ゼロから複雑さに挑戦するという覚悟が必要だと私は考えています。

しかし、これは決してクライアントサイドルーティングを使ってはいけないと言いたいわけではありません。

クライアントサイドで複雑な状態を管理し、優れたユーザー体験を提供するためには、クライアントサイドルーティングのような技術が今もなお必要とされています。それらを利用することで実現可能な新しい体験や価値を、私たち開発者は追求し続けるべきだと考えています。

アクセシビリティを考えることは個々の開発者にとって非常に重要ですが、使用しているフレームワーク自体がアクセシビリティをどのように取り扱っているかを理解することも重要です。

最後に、Nuxt.jsにおけるアクセシビリティの取り組みについて触れてこの発表を終わろうと思います。

実はNuxt.jsのロードマップを調べると、アクセシビリティに関連する項目が含まれていることが分かります。

スクリーンショット:Nuxt.jsのアクセシビリティロードマップが書かれたGitHub Issueページ

例えば、先ほど紹介したARIAライブリージョンの実装や、アクセシビリティテストエンジンである「axe-core」をビルトインする、ナビゲーション時のフォーカス管理など、様々なタスクが挙げられています。

今後、Nuxt.jsのアクセシビリティへの取り組みに・ロードマップの進展を見守りつつ、私たち自身もアクセシビリティ向上のために努めていく必要があると思っております。

以上で発表を終わります。ご清聴いただきありがとうございました。

参考資料