成品連結:Speech Synthesis、操作前程式碼、完成後程式碼
今天要做的作品與前幾天做過的語音辨識類似,不過今天要做的是閱讀文字。閱讀文字同樣用到了瀏覽器內建的 Web Speech API,不過目前僅有 Google Chrome 支援,在其他瀏覽器會沒有效果。
首先可以看到 JS 已經預先宣告了幾個 DOM 元素,其中包含一項 const msg = new SpeechSynthesisUtterance();
也就是今天操作的主角,我們會利用 SpeechSynthesisUtterance()
來設置要閱讀的文字、閱讀速度、音調、以及選擇的語音。
這裡我們可以先設置 msg.text
,也就是要被讀出的文字
msg.text = document.querySelector('textarea').value;
我們可以藉由已宣告的 msg
來設置語音,但在那之前需要先取得所有可用的語音。而取得語音會用到 speechThesis.getVoices()
這個方法
但如果你直接下了這個語法 voices = speechThesis.getVoices()
,會發現 voices
仍舊空空如也,這是因為 speechThesis
需要搭配監聽事件 voiceschanged
才會有效果
function populateVoices() {
// code here
}
speechSynthesis.addEventListener('voiceschanged', populateVoices);
function voiceschanged
當中就可以填入剛剛沒有效果的 voices = speechThesis.getVoices()
,或是你可以寫 voices = this.getVoices()
function populateVoices() {
voices = this.getVoices();
}
speechSynthesis.addEventListener('voiceschanged', populateVoices);
這時候 voices
裡面多了許多語音!(如果你使用 Windows 或是其他作業系統,看到的數量可能會不同)每一項的格式看起來像這樣
使用 array 的方法 filter()
過濾出英語發音的語音
function populateVoices() {
voices = this.getVoices();
const voiceOptions = voices
.filter(voice => voice.lang.includes('en'));
}
接著渲染至 HTML 中的 voicesDropdown
。你可以使用 innerHTML 或是 createElement 然後再 append 到 HTML,這裡我用 innerHTML
function populateVoices() {
voices = this.getVoices();
const voiceOptions = voices
.filter(voice => voice.lang.includes('en'))
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
voicesDropdown.innerHTML = voiceOptions;
}
如果我的 JS30 前幾篇也都有看的話應該對這種鏈狀的寫法不陌生。
msg
的語音當使用者從選單選到語音時,要將該語音加至 msg.voice
,如此到時候閱讀時才會用此語音讀出聲音。要從一個 array 中找到特定項目很適合用 find()
這個方法
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
}
voicesDropdown.addEventListener('change', setVoice);
現在當你在 console 輸入 speechSynthesis.speak(msg)
應該就會聽到聲音了,換個聲音再試試看吧!
現在我們要把 speechSynthesis.speak(msg)
這個功能寫入 function,方便隨時呼叫(像是在更換語音、頻率、速度時)
function toggle() {
speechSynthesis.cancel();
speechSynthesis.speak(msg);
}
第一個方法 speechSynthesis.cancel()
可以將目前的所有的 SpeechSynthesisUtterance()
都清除,清除後我們再接著播放(配合新的聲音、速度、頻率等等...)
我們可以將這個 function 放在剛剛建立的 setVoice
中執行
function setVoice() {
msg.voice = voices.find(voice => voice.name === this.value);
toggle(); // 語音設置好後閱讀文字
}
可以看到已有變數儲存 兩個 input
及一個 textarea
,先建立監聽事件
function setOption() {
// code here
}
options.forEach(option => option.addEventListener('input', setOption));
從這幾個元素的name
屬性可以看到分別是 rate
、pitch
、text
,剛好跟 SpeechSynthesisUtterance()
中的屬性名稱符合!我們可以善用這點來進行修改
function setOption() {
msg[this.name] = this.value;
toggle();
}
options.forEach(option => option.addEventListener('input', setOption));
this.value
的值會是input[type="range"]
中的數值以及 textarea
的文字內容,設定完成後一樣執行 toggle()
來到最後的部分了!這裡相對簡單,只要設定監聽事件並執行 toggle()
就好。
等一下!開始閱讀好處理,但是停止閱讀呢?toggle()
沒辦法停止啊!
沒有錯,所以我們需要修改一下原本的 toggle()
一下。我們要在帶入參數中設定 boolean,如果是 true
才開始閱讀,反之只會執行 speechSynthesis.cancel()
停止閱讀
function toggle(startOver = true) {
speechSynthesis.cancel();
if (startOver) {
speechSynthesis.speak(msg);
}
}
startOver = true
是 ES6 的寫法,意思是預設 startOver
的值是 true
,所以如果沒有帶入任何參數會預設帶入 true
最後來綁定事件吧!
speakButton.addEventListener('click', toggle);
stopButton.addEventListener('click', () => toggle(false));
stopButton
的 callback 看起來奇怪,其實意思與下方一樣
stopButton.addEventListener('click', function(e) {
toggle(false);
});
完成!