サイトアイコン IT & ライフハックブログ|学びと実践のためのアイデア集

WCAG 2.2を踏まえた音声読み上げUIの実装|要件・ARIA・Web Speech API・テスト

close up on woman making heart sign

Photo by RDNE Stock project on Pexels.com

Webページに「本文を音声で読む」機能を付けるとき、重要なのは音が出ることだけではありません。再生や停止を迷わず操作でき、現在の状態が見た目でも支援技術でも分かり、キーボードやポインタでも扱えることが必要です。

先に結論を述べると、WCAG 2.2は、すべてのWebサイトに音声読み上げ機能の追加を求めているわけではありません。ただし、TTSを提供するなら、そのコントロール自体をアクセシブルに実装する必要があります。また、3秒を超える音声を自動再生する場合は、停止・一時停止、またはシステム音量とは独立した音量調整が必要です。コンポーネント単体のコードだけでサイト全体の「WCAG 2.2準拠」を保証することはできないため、最後に実環境で検証します。

TTSとスクリーンリーダーは同じものではない

最初に、混同しやすい用語を分けます。ここを曖昧にすると、誰のどの困りごとを解決する機能なのか判断しにくくなります。

用語 役割 実装時の考え方
音声読み上げ(TTS) 指定した文章を合成音声に変換する、サイト側の付加機能 開始・一時停止・再開・停止を明確にし、未対応環境でも本文を読める状態を保つ
スクリーンリーダー ページ構造や操作部品を読み上げ、移動や入力も支援する支援技術 見出し、リンク、ラベル、フォーカス順、状態をHTMLとARIAで正しく伝える
WCAGへの適合 ページまたは一連のページを、対象レベルの全達成基準で評価した結果 一つの部品だけで判断せず、コンテンツ、操作、表示、支援技術で総合的に試験する

スクリーンリーダーの仕組みや確認環境を先に知りたい場合は、サイト内のスクリーンリーダー対応の実装とテストも参照してください。

音声読み上げUIに関係するWCAG 2.2の達成基準

達成基準は、機能が存在するだけで一律に適用されるものではありません。次の表では、音声読み上げUIとの関係と、実装時に確認する内容を分けて整理します。WCAG 2.2で追加された基準の全体像は、サイト内のWCAG 2.2の追加達成基準の解説で確認できます。

達成基準 音声読み上げUIとの関係 確認内容
1.4.2 Audio Control(A) 3秒を超える音声を自動再生する場合に適用される。利用者が明示的に開始するTTSは、この条件だけでは対象にならない 自動再生を避ける。行う場合は停止・一時停止、または独立音量を提供する
2.2.2 Pause, Stop, Hide(A) 音声そのものではなく、自動で動く・点滅する・スクロールする・更新される情報が対象 読み上げと同時にハイライトや進行表示を自動更新するなら、条件に応じて停止や非表示を用意する
4.1.2 Name, Role, Value(A) 再生ボタン、停止ボタン、スライダーなどの名前・役割・状態を支援技術に伝える まずネイティブHTMLを使い、ラベルと状態をコードから取得できるようにする
4.1.3 Status Messages(AA) 「読み上げ中」「停止しました」など、フォーカスを移さず表示する状態を支援技術にも伝える role="status"などを使い、過剰な割り込みを避ける
2.4.7 Focus Visible(AA)と2.4.11 Focus Not Obscured(AA) キーボード利用者が現在位置を確認でき、固定ヘッダーなどに完全に隠れないようにする フォーカス表示を消さず、ページ全体をTab移動して重なりを確認する
2.5.8 Target Size(AA) ポインタ操作の対象は原則24×24 CSSピクセル以上、または規定の間隔・例外を満たす必要がある 再生・停止ボタンを小さく密集させず、押し間違えにくい余白を確保する
2.5.7 Dragging Movements(AA) カスタムシークなどでドラッグ操作を使う場合に、ドラッグ不要の単一ポインタ操作も求められる トラックのクリック、前後移動ボタンなど、機能に合う代替操作を用意する

2.4.13 Focus AppearanceはAAAの基準です。AAを目標にする場合でも、十分な大きさとコントラストを持つフォーカス表示は有効ですが、達成レベルを混同しないようにします。

操作設計はネイティブHTMLから始める

再生や停止は、見た目をボタンらしくした<div>ではなく、最初から<button type="button">で実装します。これにより、標準のキーボード操作、フォーカス、役割を利用できます。独自ARIAは、HTMLだけでは表せない情報を補う場合に限定します。

ARIAの役割をさらに確認したい場合は、サイト内の音声読み上げに配慮したARIA属性の使い分けも役立ちます。

Web Speech APIによる最小実装例

次の例は、利用者の操作で本文の読み上げを始める構成です。コードは学習用の最小例であり、これだけでWCAG適合を保証するものではありません。WordPressへ組み込む際は、テーマやプラグインのスクリプト管理方法に合わせて配置してください。

HTML

<section aria-labelledby="tts-heading">
  <h2 id="tts-heading">本文の音声読み上げ</h2>
  <div class="tts-controls" role="group" aria-label="音声読み上げ操作">
    <button type="button" id="tts-play">読み上げる</button>
    <button type="button" id="tts-stop">停止</button>

    <label for="tts-volume">音量(次回の読み上げから)</label>
    <input id="tts-volume" type="range" min="0" max="1" step="0.1" value="1">

    <label for="tts-rate">速度(次回の読み上げから)</label>
    <input id="tts-rate" type="range" min="0.5" max="2" step="0.1" value="1">
  </div>

  <p id="tts-status" role="status" aria-atomic="true">待機中</p>
  <p id="tts-unsupported" hidden>この環境では音声読み上げを利用できません。本文はそのままお読みいただけます。</p>

  <div id="tts-target">
    <p>ここに読み上げ対象の本文を置きます。</p>
  </div>
</section>

JavaScript

const synth = window.speechSynthesis;
const playButton = document.getElementById('tts-play');
const stopButton = document.getElementById('tts-stop');
const target = document.getElementById('tts-target');
const status = document.getElementById('tts-status');
const unsupported = document.getElementById('tts-unsupported');
const volume = document.getElementById('tts-volume');
const rate = document.getElementById('tts-rate');
let stopRequested = false;

const canSpeak = 'speechSynthesis' in window
  && typeof SpeechSynthesisUtterance === 'function';

if (!canSpeak) {
  playButton.hidden = true;
  stopButton.hidden = true;
  unsupported.hidden = false;
}

playButton.addEventListener('click', () => {
  if (synth.speaking && synth.paused) {
    synth.resume();
    playButton.textContent = '一時停止';
    status.textContent = '読み上げを再開しました';
    return;
  }

  if (synth.speaking) {
    synth.pause();
    playButton.textContent = '再開';
    status.textContent = '読み上げを一時停止しました';
    return;
  }

  const utterance = new SpeechSynthesisUtterance(target.textContent.trim());
  stopRequested = false;
  utterance.volume = Number(volume.value);
  utterance.rate = Number(rate.value);
  utterance.onstart = () => {
    playButton.textContent = '一時停止';
    status.textContent = '読み上げを開始しました';
  };
  utterance.onend = () => {
    if (stopRequested) return;
    playButton.textContent = '読み上げる';
    status.textContent = '読み上げを終了しました';
  };
  utterance.onerror = (event) => {
    if (stopRequested || event.error === 'canceled') return;
    playButton.textContent = '読み上げる';
    status.textContent = '読み上げを開始できませんでした';
  };

  synth.cancel();
  synth.speak(utterance);
});

stopButton.addEventListener('click', () => {
  stopRequested = true;
  synth.cancel();
  playButton.textContent = '読み上げる';
  status.textContent = '読み上げを停止しました';
});

CSSで確認する最小項目

.tts-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

.tts-controls button {
  min-inline-size: 24px;
  min-block-size: 24px;
  padding: 0.5rem 0.75rem;
}

.tts-controls :focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 3px;
}

MDNのSpeechSynthesisリファレンスでは、speak()、pause()、resume()、cancel()などの動作を確認できます。利用できる声や読み上げ方は端末とブラウザの環境に依存するため、対象環境での確認が必要です。

このサンプルで意図的に省いたもの

公開前の受け入れテスト

  1. キーボード:TabとShift+Tabで全操作に到達できるか、EnterまたはSpaceでボタンを実行できるか、スライダーを矢印キーで変更できるか確認します。
  2. スクリーンリーダー:再生・停止ボタンの名前と役割、スライダーのラベルと値、開始・一時停止・再開・終了の状態が理解できるか確認します。
  3. フォーカス表示:フォーカスが見え、固定ヘッダー、Cookie通知、ダイアログなどに完全に隠れないか、ページ全体を移動して確認します。
  4. ポインタとタッチ:ボタンが小さすぎず、隣接する操作を誤って押しにくいか確認します。カスタムのドラッグ操作があれば、ドラッグ不要の代替も試します。
  5. 失敗時:未対応環境、音声が取得できない環境、読み上げエラーでも本文が消えず、利用者が通常どおり読めるか確認します。
  6. 複数環境:一つのブラウザだけで終えず、対象とするOS、ブラウザ、スクリーンリーダーの組み合わせで結果を記録します。

実務チェックリスト

まとめ

音声読み上げUIは、TTSを追加するだけでは完成しません。自動再生の有無を切り分け、ネイティブHTMLで操作部品を作り、状態通知、可視フォーカス、ターゲットサイズ、失敗時の代替を順に確認することが要点です。まずは小さな構成で実装し、利用者が実際に使う環境で検証してから機能を増やしてください。

参考資料

音声読み上げUIは、再生ボタンだけでなく、状態通知やキーボード操作まで確かめて初めて使いやすくなります。株式会社greedenのWebアクセシビリティサービス UUUは、読み上げ補助の追加とアクセシビリティチェックを支援します。

モバイルバージョンを終了