JS 30 是由加拿大的全端工程師 Wes Bos 免費提供的 JavaScript 簡單應用課程,課程主打 No Frameworks
、No Compilers
、No Libraries
、No Boilerplate
在30天的30部教學影片裡,建立30個JavaScript的有趣小東西。
另外,Wes Bos 也很無私地在 Github 上公開了所有 JS 30 課程的程式碼,有興趣的話可以去 fork 或下載。
還記得在 Day 20 - Native Speech Recognition,我們使用了 Web Speech API 中語音識別(Speech recognition)的部分,而這次我們要使用的是語音合成(Speech synthesis)的部分。
這次的主角是Web Speech API
的SpeechSynthesis
、SpeechSynthesisUtterance
。
在下面的範例,我們會利用 API 語音合成功能,讓網頁用不同的聲音讀出textarea
裡面的文字。
select(#voices
)是一個用來選擇要用什麼聲音讀出文字的選單。
input([name="rate"]
) 是用來調整讀字的速度。
input([name="rate"]
) 是用來調整讀字音高。
textarea([name="text"]
) 是要讀的文字。
button(#stop
) 是停止讀的按鈕。
button(#speak
) 是開始讀的按鈕。
<div class="voiceinator">
<h1>The Voiceinator 5000</h1>
<select name="voice" id="voices">
<option value="">Select A Voice</option>
</select>
<label for="rate">Rate:</label>
<input name="rate" type="range" min="0" max="3" value="1" step="0.1">
<label for="pitch">Pitch:</label>
<input name="pitch" type="range" min="0" max="2" step="0.1">
<textarea name="text">Hello! I love JavaScript ?</textarea>
<button id="stop">Stop!</button>
<button id="speak">Speak</button>
</div>
宣告常數 msg 並建立一個SpeechSynthesisUtterance
物件,這個物件主要包含兩種資訊,一個是要讀出的內容(what to read),另一個是要如何讀它(how to read)。
宣告空陣列 voices,之後我們會放入由SpeechSynthesis.getVoices()
取得的聲音種類陣列。
後面的四個常數分別取得聲音種類選單、調整速度/音高的滑動桿、開始讀出文字按鈕、停止讀出文字按鈕。
const msg = new SpeechSynthesisUtterance(); //come from WEB Speech API
let voices = [];
const voicesDropdown = document.querySelector('[name="voice"]');
const options = document.querySelectorAll('[type="range"], [name="text"]');
const speakButton = document.querySelector('#speak');
const stopButton = document.querySelector('#stop');
text
是SpeechSynthesisUtterance
的一個屬性,用來指定要讀出的內容,下面我們指定要讀出的內容是textarea
的文字。
msg.text = document.querySelector('[name="text"]').value;
接下來我們需要取得網頁支援的聲音種類並把它們做成選項放到選單裡面。
在做選單前,我們還要確保真的有拿到聲音種類的陣列,所以在speechSynthesis
註冊voiceschanged event listener
以populateVoices()
作為事件處理函式。
在populateVoices()
裡,我們首先將取得的聲音陣列放到voices
裡面,在 console 印出陣列內容的話,可以發現陣列中的元素都是SpeechSynthesisVoice
物件,這個物件擁有該種聲音的名稱、語言等等的屬性。
然後我們可以把 voices 陣列進行 map,把每種聲音都做成選項,因為 map 結束是一個陣列,還要加上join()
讓它變成 HTML 格式的大字串,之後才能放入voicesDropdown
當作選項。
function populateVoices(){
voices = this.getVoices();
console.log(voices);
voicesDropdown.innerHTML = voices
.map(voice => `<option value="${voice.name}">${voice.name} (${voice.lang})</option>`)
.join('');
}
speechSynthesis.addEventListener('voiceschanged',populateVoices);
voices
陣列的內容 :
toggle()
用來開始讀出文字或停止讀出文字,我們預設參數startOver = true
表示在正常情況下都要開始讀文字。方法最上面寫speechSynthesis.cancel();
的功能是停止讀文字,因為希望每一次讀都是重新開始讀。
function toggle(startOver = true){
speechSynthesis.cancel(); // stop speaking
if(startOver){
speechSynthesis.speak(msg); // restart speaking
}
}
在選單(voiceDropdown
)註冊change event listener
,用來把讀文字的聲音種類,改成我們選擇的那種。
事件處理函式setVoice()
,用來調整讀文字時的聲音種類。前面有說過SpeechSynthesisUtterance
可以決定 how to read,所以它有一個voice
屬性可以指定聲音種類。
要注意的是我們指定的聲音種類要是一個SpeechSynthesisVoice
物件而不是給它聲音的名稱就好,所以還要在voices
陣列尋找擁有和選項的value
一樣聲音名稱(name)的SpeechSynthesisVoice
物件。
在方法最後呼叫toggle()
,就可以發現隨著每一次選的選項不同,它會重新讀一次文字。
function setVoice(){
console.log('changing voice');
msg.voice = voices.find(voice => voice.name === this.value);
toggle();
}
voicesDropdown.addEventListener('change', setVoice);
倒數第二個要處理的是讀文字時的速度和音高調整,因為SpeechSynthesisUtterance
本身有pitch
和rate
屬性可供修改,所以我們只要拿設定好的<input>
的 name 和 value 來修改SpeechSynthesisUtterance
對應的屬性值就好。
在兩個<input>
上都註冊change event listener
,可以讓每一次滑桿值有改變時,就重新指定聲音屬性並重新開始讀文字(別忘記在setOption()
的最後放入toggle()
)。
function setOption(){
console.log(this.name,this.value);
msg[this.name] = this.value;
toggle();
}
options.forEach(option => option.addEventListener('change',setOption))
最後就是關於開始和停止讀文字鈕的部分啦~
開始讀文字的按鈕比較簡單,只要註冊click event listener
然用toggle()
處理就好。
認真要說的話,停止讀文字的按鈕也沒有難到哪裡,只是這邊我們不能直接用... , toggle(false)
,而是要寫... , ()=>toggle(false)
才會有停止讀文字的效果。
speakButton.addEventListener('click',toggle);
stopButton.addEventListener('click',() => toggle(false));
Web Speech API
SpeechSynthesis
SpeechSynthesisUtterance
SpeechSynthesisVoice