スクリーンリーダーを起動せずにコンテンツを読み上げたい
はじめに
「Webページに文章書いてあるんだから読めば誰だってわかるでしょ。」と思ったことはありますか?顕在的に思ったことがないとしても、潜在的にそう思って実装してしまうことは多いのではないでしょうか。
そこに文章が書いてあっても、いろいろな事情でそれが読めない人は存在します。
例えば、視覚に困難をお持ちの方。全く見えないことや文字を拡大しても読めないことが想定できます。また、認知能力に困難をお持ちの方。漢字が読めないことが想定できます。
世界に目を向けると識字率の問題も出てきます。インターネットにアクセスできても文字を読むことが困難な人は存在します。
そういった人たちにもコンテンツを届けるにはどうしたら良いでしょうか。
その一つの手段として「コンテンツの読み上げ機能」があると思います。スクリーンリーダーを使用すればそれが実現可能ですが、使い慣れてない人にとっては操作自体が困難になってしまいます。
本記事は、スクリーンリーダーを使用しない読み上げ機能を実装しようという記事です。
対象読者
- 読み上げ機能を実装したい人
- アクセシビリティに興味がある人
実装環境
"dependencies": {
"next": "15.0.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"zustand": "^5.0.1"
}
読み上げまでの大まかな流れ
スクリーンリーダーを起動せずにコンテンツを読み上げるまでの大まかな流れは下記のとおりです。
- 読み上げる要素の取得
- 読み上げの設定
- 読み上げる
実装例を見ながら具体的に確認していきましょう。
読み上げる要素の取得
ref
経由で取得します。
読み上げたいテキストの親にref
を付与し、innerText
経由で取得することができます。
下記の例だとref.current.innerText
は、読み上げたい文章をここに
となります。
export function Contents() {
const ref = useRef<HTMLParagraphElement | null>(null);
function getTextForSpeech() {
if(!ref.current) return;
return ref.current.innerText;
}
return <p ref={ref}>読み上げたい文章をここに</p>
}
タグが入れ子になっている場合はこんな風にref
を付与します。
下記の例だとref.current.innerText
は、タイトル\n\n読み上げたい文章をここに
となります。
return (
<div ref={ref}>
<h1>タイトル</h1>
<p>読み上げたい文章をここに</p>
</div>
)
innerText
ではなくtextContent
でも取得することができますが、結果が異なるので注意してください。MDNに両者の違いが説明されているので引用します。
読み上げの設定
読み上げたい要素を取得できたら、次は読み上げる準備です。
Web Speech APIが提供しているSpeechSynthesisUtterance
クラスを使用します。
初期化する際に、引数へ読み上げたいテキスト情報を渡してあげます。インスタンスには言語を設定するlang
と読み上げ速度を設定するrate
というプロパティが存在するので読み上げたい内容に合わせて設定します。
function prepareSpeech() {
if (!ref.current) return;
// 初期化
const utterance = new SpeechSynthesisUtterance(getTextForSpeech(ref.current));
// 言語は日本語を指定
utterance.lang = 'ja-JP';
// 読み上げ速度は通常
utterance.rate = 1;
return utterance;
}
他にもpitch
やvoice
なども設定できるので気になる人はMDNを確認してみてください。
読み上げる
読み上げるには、WindowオブジェクトのspeechSynthesis
プロパティが持つspeak
メソッドに前ステップで初期化したSpeechSynthesisUtterance
インスタンスを渡すことで実現します。
function startSpeech() {
const utterance = prepareSpeech();
if(!utterance) return;
// 読み上げる
window.speechSynthesis.speak(utterance);
}
このメソッドをbuttonタグのonClick等に接続することで、ユーザーがボタンをクリックした時に読み上げることができます。
ただ、この実装だと連続でボタンをクリックされた際に、2回目の読み上げが1回目の読み上げが終わるまで開始されないので、直前でcancel
メソッドを呼ぶことにしました。
function startSpeech() {
const utterance = prepareSpeech();
if(!utterance) return;
// 直前の読み上げを停止する
window.speechSynthesis.cancel();
// 読み上げる
window.speechSynthesis.speak(utterance);
}
完成
以上で読み上げ機能は完成です!
おわりに
いかがでしたでしょうか。
「Webページに文章書いてあるんだから読めば誰だってわかるでしょ。」だとほとんどのユーザーは理解できる内容かもしれませんが、すべてのユーザーが理解できるようにはなりません。
一人でも多くの人が理解できるように、目以外でも理解できる内容になっているかということを意識していくことも、プロダクトを開発するエンジニアとして忘れてはいけないことだと考えるキッカケになりました。
今後もすべてのユーザーのためになる技術をたくさん学んでいこうと思います。
読み上げ機能を搭載したアンケート回答アプリのデモを作ってみたので興味がある人は是非覗いてみてください。
デモアプリ
ソースコード
Discussion