Voice Button

Web Speech API を使った、テキスト入力欄のための音声入力です。

使いどころ:音声を、テキスト欄を埋める もう一つ の手段として使いたいとき。置き換えとしてではありません。入力し続けられることは保たれなければなりません。音声だけの経路は、発話に違いのある人、騒がしい部屋にいる人、そして認識機能をまったく持たないブラウザの利用者を締め出します。

マイクボタン、それだけです。取り込みを担い、聞き取った内容を報告します。そのテキストをどこへ入れるかは呼び出し側の判断です。入力欄まで書き込むコンポーネントは、どの入力欄か、追記か置換か、キャレットをどうするかを知らなければならず、その三つの答えはアプリごとに違うからです。

クイックスタート

<r-voice-button label="音声入力を開始" active-label="音声入力を停止"></r-voice-button>
const mic = document.createElement('r-voice-button');
mic.label = '音声入力を開始';
mic.activeLabel = '音声入力を停止';
const input = document.querySelector('textarea');
let base = '';

mic.addEventListener('voicestart', () => {
  // 打ち込まれた文字と話された文字のあいだに空白を一つ。すでにあるなら足しません。
  base = input.value === '' || /\s$/.test(input.value) ? input.value : `${input.value} `;
});

mic.addEventListener('voiceresult', (event) => {
  input.value = base + event.detail.transcript;
});

composer.append(mic);

その背後にある判断

最新の断片ではなく、取り込み全体を報告します

途中経過の結果は、認識が進むにつれて書き換えられます。「你好」は「你好世界」になるのであって、「世界」を運ぶ二つ目のイベントが増えるのではありません。イベントごとに追記する使い方をすると、 你好你好世界 になってしまいます。欄にすでにあったテキストを覚えておき、一度だけ連結してください。

送信はしません

認識は十分に頻繁に間違えるので、話し手に代わって確定してしまうと、必要な見直しの機会を奪います。この要素は欄を埋めるところまでで止まります。送信は意図した行為のままです。

認識が存在しない場所では自分を隠します

Firefox は音声認識を持っておらず、その API がないブラウザも同じです。認識が使えないとき、この要素は disabled で自分を無効にするのではなく hidden で自分を隠します。disabled は「機能はあるが一時的に使えない」を意味するのに対し、この環境にそもそも機能がないなら、ボタンを消すほうが正確だからです。決して働かないボタンを見せれば、何も起きないタップを誘い、そのあと説明が要ることになります。

四つのエラーのうち、見せる価値があるのは二つだけ

種類 何であるか 見せる?
denied マイクが拒否された はい(対処できる)
failed それ以外の何かが失敗した はい
noSpeech 沈黙の間 いいえ
aborted プログラムからの停止 いいえ

後ろの二つは本物の失敗と同じ経路で届きますが、失敗ではありません。これらを表に出すと、本当の失敗のときだけでなく、ごく普通の取り込みのたびにエラーが出てしまいます。

アクセシビリティ

アイコンだけでなく、アクセシブルな名前が状態とともに変わりaria-pressed が切り替えを運びます。スクリーンリーダーはアイコンではなく「音声入力を停止、押されています」と読み上げます。 Escape は取り込みを破棄します。確定はしません。文の途中で言い間違いに気づいた話し手が望むのはそちらです。

聞き取り中の状態は枠線、塗り、そして リングで伝えられるので、色だけに頼りません。動くのはリングだけで、それは装飾です。prefers-reduced-motion では、情報を失わないままリングが落ちます。

言語はページに従います

lang取り込みごとに読まれ、既定では文書のものになります。セッションの途中でロケールを切り替えるアプリは、表示している言語で書き取ります。

API リファレンス

プロパティ

プロパティ 既定値 説明
lang string 文書のもの 話される言語の BCP 47 タグ。取り込みごとに読まれます。
continuous boolean true 最初の沈黙で止めず、間をまたいで聞き続けます。
disabled boolean false ボタンを無効にします。start() は無視され、内側のボタンも無効になります。進行中の取り込みは止めません。
label string 'Start voice input' 待機中のアクセシブルな名前。
activeLabel string 'Stop voice input' 聞き取り中のアクセシブルな名前。
listening boolean false 読み取り専用、反映されます。:host([listening]) でスタイルを当てられます。
supported boolean 読み取り専用。この環境が音声を認識できるかどうか。
sheet string '' 要素の shadow DOM に注入する CSS。

メソッド

start() · stop()(認識できた分は残します) · abort()(破棄します) · toggle()

toggle() は反映された属性ではなく認識器自身の状態を読みます。報告せずに始まってしまった取り込みがあると、両者は食い違ったままになり、次の起動は二つ目の取り込みを開こうとして断られ、何も起きません。

イベント

イベント detail 発生するとき
voicestart 取り込みが始まった
voiceresult { transcript, isFinal } テキストが届いた、または書き換えられた
voiceerror { kind, detail } 環境が問題を報告した
voiceend 理由を問わず、取り込みが終わった

Part

buttonicon

スタイリング

<r-voice-button> は自前の CSS カスタムプロパティを 20 個、そしてテーマから読むセマンティックトークンを公開しています。継承が届く場所ならどこにでも設定できます(:root、外側のコンテナ、要素)。

r-voice-button {
  --ran-voice-background: var(--ran-color-bg-subtle);
}

Part:button · hint · icon

一覧はスタイルトークンに、どのトークンを選ぶかはデザインシステムにあります。

関連