JS純前端實(shí)現(xiàn)瀏覽器語(yǔ)音播報(bào)、朗讀功能的完整代碼
一、朗讀單條文本:
① 語(yǔ)音自選參數(shù),按鈕控制語(yǔ)音:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本轉(zhuǎn)語(yǔ)音播報(bào)</title>
<style>
body {
background: #f0f0f0;
text-align: center;
}
.select {
display: flex;
justify-content: center;
align-items: center;
}
button {
font-size: 18px;
}
#status {
margin: 20px;
color: yellowgreen;
}
</style>
</head>
<body>
<div style="color: rgb(248, 74, 103);">文本轉(zhuǎn)語(yǔ)音播報(bào):此功能依賴瀏覽器支持Web Speech API,目前主流瀏覽器(Chrome、Edge等)均已支持</div>
<div>
<h2>輸入要朗讀的文本:</h2>
<textarea id="textToSpeak" placeholder="請(qǐng)輸入需要朗讀的文本內(nèi)容..." rows="20" cols="100"></textarea>
</div>
<div class="select">
<h3>選擇語(yǔ)音:</h3>
<select id="voiceSelect">
<option value="">加載中...</option>
</select>
</div>
<div class="select">
<h3>語(yǔ)速:</h3>
<input type="range" id="rate" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="select">
<h3>音調(diào):</h3>
<input type="range" id="pitch" min="0.5" max="2" step="0.1" value="1">
</div>
<div class="select">
<h3>音量:</h3>
<input type="range" id="volume" min="0" max="1" step="0.1" value="1">
</div>
<!-- 控制按鈕區(qū)域 -->
<div>
<button id="startBtn">開始朗讀</button>
<button id="pauseBtn" disabled>暫停</button>
<button id="resumeBtn" disabled>繼續(xù)</button>
<button id="cancelBtn" disabled>停止</button>
</div>
<!-- 狀態(tài)顯示區(qū)域 -->
<div id="status"></div>
</body>
<script>
// 檢查瀏覽器是否支持Web Speech API
if ('speechSynthesis' in window) {
const synth = window.speechSynthesis;
let voices = [];
// DOM元素
const textInput = document.getElementById('textToSpeak');
const voiceSelect = document.getElementById('voiceSelect');
const rateInput = document.getElementById('rate');
const pitchInput = document.getElementById('pitch');
const volumeInput = document.getElementById('volume');
const rateValue = document.getElementById('rateValue');
const pitchValue = document.getElementById('pitchValue');
const volumeValue = document.getElementById('volumeValue');
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const cancelBtn = document.getElementById('cancelBtn');
const statusDisplay = document.getElementById('status');
// 獲取可用語(yǔ)音列表
function loadVoices() {
voices = synth.getVoices();
voiceSelect.innerHTML = '';
// 篩選中文語(yǔ)音優(yōu)先顯示
const chineseVoices = voices.filter(voice =>
voice.lang.includes('zh') || voice.name.includes('Chinese')
);
const otherVoices = voices.filter(voice =>
!voice.lang.includes('zh') && !voice.name.includes('Chinese')
);
// 添加中文語(yǔ)音
chineseVoices.forEach(voice => {
const option = document.createElement('option');
option.textContent = `${voice.name} (${voice.lang})`;
option.value = voice.name;
voiceSelect.appendChild(option);
});
// 如果有中文語(yǔ)音,添加分隔線
if (chineseVoices.length > 0 && otherVoices.length > 0) {
const separator = document.createElement('option');
separator.textContent = '────────── 其他語(yǔ)言 ──────────';
separator.disabled = true;
voiceSelect.appendChild(separator);
}
// 添加其他語(yǔ)音
otherVoices.forEach(voice => {
const option = document.createElement('option');
option.textContent = `${voice.name} (${voice.lang})`;
option.value = voice.name;
voiceSelect.appendChild(option);
});
// 默認(rèn)選擇第一個(gè)中文語(yǔ)音
if (chineseVoices.length > 0) {
voiceSelect.value = chineseVoices[0].name;
} else if (voices.length > 0) {
voiceSelect.value = voices[0].name;
}
}
// 初始化時(shí)加載語(yǔ)音,某些瀏覽器需要觸發(fā)一次語(yǔ)音合成才能加載語(yǔ)音列表
loadVoices();
if (speechSynthesis.onvoiceschanged !== undefined) {
speechSynthesis.onvoiceschanged = loadVoices;
}
// 顯示當(dāng)前速率、音調(diào)、音量值
rateInput.addEventListener('input', () => {
rateValue.textContent = rateInput.value;
});
pitchInput.addEventListener('input', () => {
pitchValue.textContent = pitchInput.value;
});
volumeInput.addEventListener('input', () => {
volumeValue.textContent = volumeInput.value;
});
// 開始朗讀
startBtn.addEventListener('click', () => {
if (synth.speaking) {
synth.cancel();
}
const text = textInput.value.trim();
if (text) {
statusDisplay.textContent = '正在朗讀...';
updateButtonStates(true);
const utterThis = new SpeechSynthesisUtterance(text);
// 設(shè)置語(yǔ)音
const selectedVoice = voices.find(voice => voice.name === voiceSelect.value);
if (selectedVoice) {
utterThis.voice = selectedVoice;
}
// 設(shè)置語(yǔ)音參數(shù)
utterThis.rate = parseFloat(rateInput.value);
utterThis.pitch = parseFloat(pitchInput.value);
utterThis.volume = parseFloat(volumeInput.value);
// 朗讀結(jié)束時(shí)的回調(diào)
utterThis.onend = () => {
statusDisplay.textContent = '朗讀完成';
updateButtonStates(false);
};
// 朗讀出錯(cuò)時(shí)的回調(diào)
utterThis.onerror = (event) => {
statusDisplay.textContent = `朗讀出錯(cuò): ${event.error}`;
updateButtonStates(false);
};
synth.speak(utterThis);
} else {
statusDisplay.textContent = '請(qǐng)輸入要朗讀的文本';
}
});
// 暫停朗讀
pauseBtn.addEventListener('click', () => {
if (synth.speaking) {
if (synth.paused) {
// 如果已經(jīng)暫停,則繼續(xù)
synth.resume();
statusDisplay.textContent = '繼續(xù)朗讀...';
pauseBtn.disabled = false;
resumeBtn.disabled = true;
} else {
// 暫停朗讀
synth.pause();
statusDisplay.textContent = '已暫停';
pauseBtn.disabled = true;
resumeBtn.disabled = false;
}
}
});
// 繼續(xù)朗讀
resumeBtn.addEventListener('click', () => {
if (synth.paused) {
synth.resume();
statusDisplay.textContent = '繼續(xù)朗讀...';
pauseBtn.disabled = false;
resumeBtn.disabled = true;
}
});
// 停止朗讀
cancelBtn.addEventListener('click', () => {
synth.cancel();
statusDisplay.textContent = '已停止';
updateButtonStates(false);
});
// 更新按鈕狀態(tài)
function updateButtonStates(isSpeaking) {
startBtn.disabled = isSpeaking;
pauseBtn.disabled = !isSpeaking;
cancelBtn.disabled = !isSpeaking;
resumeBtn.disabled = true;
}
} else {
// 瀏覽器不支持Web Speech API時(shí)的處理
document.getElementById('status').textContent = '抱歉,您的瀏覽器不支持語(yǔ)音合成功能,請(qǐng)使用Chrome、Edge等現(xiàn)代瀏覽器。';
// 禁用所有控制按鈕
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
button.disabled = true;
button.classList.add('opacity-50', 'cursor-not-allowed');
});
// 禁用選擇框
document.getElementById('voiceSelect').disabled = true;
const sliders = document.querySelectorAll('input[type="range"]');
sliders.forEach(slider => {
slider.disabled = true;
});
}
</script>
</body>
</html>
② 效果圖:

二、朗讀多條文本:
① 語(yǔ)音有默認(rèn)值:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>文本轉(zhuǎn)語(yǔ)音播報(bào)(后端數(shù)據(jù))</title>
<style>
body {
background: #f0f0f0;
text-align: center;
padding: 20px;
font-family: Arial, sans-serif;
}
.control-panel {
margin: 20px 0;
}
button {
font-size: 18px;
padding: 8px 16px;
margin: 0 5px;
cursor: pointer;
border: none;
border-radius: 4px;
background: #4CAF50;
color: white;
}
button:disabled {
background: #cccccc;
cursor: not-allowed;
}
#status {
margin: 20px auto;
color: #333;
padding: 10px;
background: #e8f0fe;
display: inline-block;
min-width: 300px;
border-radius: 4px;
}
.alarm-list {
max-width: 600px;
margin: 20px auto;
text-align: left;
background: white;
padding: 15px;
border-radius: 4px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.alarm-item {
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.alarm-item:last-child {
border-bottom: none;
}
.loading {
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<div style="color: rgb(248, 74, 103);">文本轉(zhuǎn)語(yǔ)音播報(bào):此功能依賴瀏覽器支持Web Speech API,目前主流瀏覽器(Chrome、Edge等)均已支持</div>
<!-- 報(bào)警信息列表預(yù)覽 -->
<div class="alarm-list">
<h4>待朗讀報(bào)警信息:</h4>
<div id="alarmContainer" class="loading">正在從后端獲取數(shù)據(jù)...</div>
</div>
<!-- 控制按鈕區(qū)域 -->
<div class="control-panel">
<button id="fetchDataBtn">獲取后端數(shù)據(jù)</button>
<button id="initVoiceBtn" disabled>初始化語(yǔ)音(必須先獲取數(shù)據(jù))</button>
<button id="startBtn" disabled>開始批量朗讀</button>
<button id="pauseBtn" disabled>暫停</button>
<button id="resumeBtn" disabled>繼續(xù)</button>
<button id="cancelBtn" disabled>停止</button>
</div>
<!-- 狀態(tài)顯示區(qū)域 -->
<div id="status">請(qǐng)點(diǎn)擊"獲取后端數(shù)據(jù)"按鈕開始</div>
</body>
<script>
// 檢查瀏覽器是否支持Web Speech API
if ('speechSynthesis' in window) {
const synth = window.speechSynthesis;
let voices = [];
let isVoiceReady = false; // 語(yǔ)音是否準(zhǔn)備就緒
let currentAlarmIndex = 0; // 當(dāng)前播放的報(bào)警索引
let alarmList = []; // 報(bào)警信息列表(后端返回的數(shù)據(jù))
let isPlaying = false; // 是否正在播放
// DOM元素
const fetchDataBtn = document.getElementById('fetchDataBtn');
const initVoiceBtn = document.getElementById('initVoiceBtn');
const startBtn = document.getElementById('startBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const cancelBtn = document.getElementById('cancelBtn');
const statusDisplay = document.getElementById('status');
const alarmContainer = document.getElementById('alarmContainer');
// 模擬從后端獲取報(bào)警信息
fetchDataBtn.addEventListener('click', async () => {
statusDisplay.textContent = '正在從后端獲取報(bào)警信息...';
fetchDataBtn.disabled = true;
try {
// 模擬API請(qǐng)求延遲
const response = await simulateBackendRequest();
if (response.success && response.data && response.data.length > 0) {
alarmList = response.data;
displayAlarmList(alarmList);
statusDisplay.textContent = `成功獲取${alarmList.length}條報(bào)警信息,請(qǐng)初始化語(yǔ)音`;
initVoiceBtn.disabled = false; // 啟用初始化語(yǔ)音按鈕
} else {
statusDisplay.textContent = '后端未返回任何報(bào)警信息';
fetchDataBtn.disabled = false;
}
} catch (err) {
statusDisplay.textContent = `獲取數(shù)據(jù)失?。?{err.message}`;
fetchDataBtn.disabled = false;
}
});
// 模擬后端請(qǐng)求
function simulateBackendRequest() {
return new Promise((resolve) => {
// 模擬網(wǎng)絡(luò)延遲
setTimeout(() => {
// 模擬后端返回的數(shù)據(jù)
const mockData = {
success: true,
data: [
{
"id": 1,
"title": "空調(diào)異常警報(bào)",
"text": "25樓北區(qū)空調(diào)參數(shù)異常:總有功功率實(shí)際值5.79kW,異常狀態(tài)累計(jì)15分0秒",
"type": 1,
"typeText": "輕微",
"timestamp": "2023-10-15 09:23:45"
},
{
"id": 2,
"title": "配電柜溫度過(guò)高",
"text": "B棟配電室3號(hào)柜溫度超標(biāo):當(dāng)前溫度62°C(閾值55°C),持續(xù)8分30秒",
"type": 2,
"typeText": "嚴(yán)重",
"timestamp": "2023-10-15 10:15:22"
},
{
"id": 3,
"title": "水泵壓力異常",
"text": "地下二層供水系統(tǒng)壓力異常:當(dāng)前壓力0.25MPa(正常范圍0.3-0.5MPa),持續(xù)12分鐘",
"type": 1,
"typeText": "輕微",
"timestamp": "2023-10-15 11:07:18"
},
{
"id": 4,
"title": "消防水箱水位不足",
"text": "1號(hào)樓消防水箱水位過(guò)低:當(dāng)前水位1.2m(最低警戒線1.5m),請(qǐng)立即處理",
"type": 3,
"typeText": "緊急",
"timestamp": "2023-10-15 13:45:03"
}
]
};
resolve(mockData);
}, 1500); // 1.5秒延遲,模擬真實(shí)網(wǎng)絡(luò)請(qǐng)求
});
}
// 在頁(yè)面上顯示報(bào)警信息列表
function displayAlarmList(alarms) {
alarmContainer.innerHTML = '';
alarms.forEach((alarm, index) => {
const item = document.createElement('div');
item.className = 'alarm-item';
item.innerHTML = `${index + 1}. ${alarm.text} <span style="color:#666;font-size:0.8em;">(${alarm.typeText})</span>`;
alarmContainer.appendChild(item);
});
}
// 初始化語(yǔ)音引擎
initVoiceBtn.addEventListener('click', async () => {
statusDisplay.textContent = '正在初始化語(yǔ)音引擎...';
initVoiceBtn.disabled = true;
try {
const loadedVoices = await getAvailableVoices();
// 篩選中文語(yǔ)音(確保有可用的中文語(yǔ)音)
const chineseVoices = loadedVoices.filter(v =>
v.lang === 'zh-CN' ||
v.lang === 'zh' ||
v.name.includes('Chinese') ||
v.name.includes('微軟') ||
v.name.includes('慧濤')
);
if (chineseVoices.length === 0) {
statusDisplay.textContent = '未找到中文語(yǔ)音包,請(qǐng)先在系統(tǒng)中安裝中文語(yǔ)音(如Windows的“微軟慧濤”)';
initVoiceBtn.disabled = false;
return;
}
// 語(yǔ)音初始化完成
isVoiceReady = true;
startBtn.disabled = false;
statusDisplay.textContent = `語(yǔ)音初始化成功!找到${chineseVoices.length}個(gè)中文語(yǔ)音包,點(diǎn)擊"開始批量朗讀"按鈕`;
console.log('可用中文語(yǔ)音:', chineseVoices);
} catch (err) {
statusDisplay.textContent = `語(yǔ)音初始化失敗:${err.message}`;
initVoiceBtn.disabled = false;
}
});
// 獲取語(yǔ)音列表(帶重試機(jī)制)
function getAvailableVoices() {
return new Promise((resolve, reject) => {
// 最多重試3次
let attempts = 0;
const checkVoices = () => {
const voices = synth.getVoices();
if (voices.length > 0) {
resolve(voices);
} else if (attempts < 3) {
attempts++;
// 觸發(fā)一次空語(yǔ)音來(lái)激活引擎
const testUtter = new SpeechSynthesisUtterance('');
synth.speak(testUtter);
setTimeout(() => {
synth.cancel();
checkVoices();
}, 500);
} else {
reject(new Error('無(wú)法加載語(yǔ)音列表,請(qǐng)刷新頁(yè)面重試'));
}
};
// 監(jiān)聽語(yǔ)音變化事件
synth.onvoiceschanged = checkVoices;
// 立即檢查一次
checkVoices();
});
}
// 開始批量朗讀
startBtn.addEventListener('click', () => {
if (!isVoiceReady || alarmList.length === 0) return;
isPlaying = true;
currentAlarmIndex = 0; // 從第一條開始
updateButtonStates(true);
statusDisplay.textContent = `準(zhǔn)備開始朗讀,共${alarmList.length}條信息`;
speakNextAlarm(); // 開始朗讀第一條
});
// 朗讀下一條報(bào)警信息
function speakNextAlarm() {
// 如果已經(jīng)讀完所有信息或已停止,則退出
if (currentAlarmIndex >= alarmList.length || !isPlaying) {
completeBatchReading();
return;
}
// 停止當(dāng)前正在播放的語(yǔ)音
if (synth.speaking) {
synth.cancel();
}
const currentAlarm = alarmList[currentAlarmIndex];
statusDisplay.textContent = `正在朗讀第${currentAlarmIndex + 1}/${alarmList.length}條:${currentAlarm.title}`;
// 創(chuàng)建語(yǔ)音實(shí)例
const utterThis = new SpeechSynthesisUtterance(currentAlarm.text);
// 獲取中文語(yǔ)音
const chineseVoices = voices.filter(v =>
v.lang === 'zh-CN' ||
v.name.includes('Chinese')
);
const selectedVoice = chineseVoices.find(voice => voice.lang === 'zh-CN') || chineseVoices[0];
if (selectedVoice) {
utterThis.voice = selectedVoice;
}
// 設(shè)置語(yǔ)音參數(shù)
utterThis.rate = 1; // 語(yǔ)速
utterThis.pitch = 1; // 音調(diào)
utterThis.volume = 1; // 音量
utterThis.lang = 'zh-CN'; // 語(yǔ)言
// 朗讀結(jié)束后處理
utterThis.onend = () => {
currentAlarmIndex++;
// 延遲500ms播放下一條,避免連在一起
setTimeout(speakNextAlarm, 500);
};
// 朗讀出錯(cuò)處理
utterThis.onerror = (event) => {
statusDisplay.textContent = `第${currentAlarmIndex + 1}條朗讀出錯(cuò):${event.error}`;
currentAlarmIndex++;
setTimeout(speakNextAlarm, 500);
};
// 開始朗讀
synth.speak(utterThis);
}
// 批量朗讀完成
function completeBatchReading() {
isPlaying = false;
statusDisplay.textContent = `所有${alarmList.length}條報(bào)警信息朗讀完成`;
updateButtonStates(false);
}
// 暫停朗讀
pauseBtn.addEventListener('click', () => {
if (synth.speaking) {
synth.pause();
statusDisplay.textContent = `已暫停在第${currentAlarmIndex + 1}條`;
pauseBtn.disabled = true;
resumeBtn.disabled = false;
}
});
// 繼續(xù)朗讀
resumeBtn.addEventListener('click', () => {
if (synth.paused) {
synth.resume();
statusDisplay.textContent = `繼續(xù)朗讀第${currentAlarmIndex + 1}條...`;
pauseBtn.disabled = false;
resumeBtn.disabled = true;
}
});
// 停止朗讀
cancelBtn.addEventListener('click', () => {
synth.cancel();
isPlaying = false;
statusDisplay.textContent = '已停止朗讀';
updateButtonStates(false);
});
// 更新按鈕狀態(tài)
function updateButtonStates(isSpeaking) {
fetchDataBtn.disabled = isSpeaking;
initVoiceBtn.disabled = isSpeaking;
startBtn.disabled = isSpeaking;
pauseBtn.disabled = !isSpeaking || synth.paused;
resumeBtn.disabled = !isSpeaking || !synth.paused;
cancelBtn.disabled = !isSpeaking;
}
} else {
// 瀏覽器不支持Web Speech API時(shí)的處理
document.getElementById('status').textContent = '抱歉,您的瀏覽器不支持語(yǔ)音合成功能,請(qǐng)使用Chrome、Edge等現(xiàn)代瀏覽器。';
// 禁用所有控制按鈕
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
button.disabled = true;
button.style.opacity = '0.5';
button.style.cursor = 'not-allowed';
});
}
</script>
</body>
</html>
② 效果圖:

三、如果沒(méi)有聲音請(qǐng)注意:
① 是否進(jìn)行了語(yǔ)音包的初始化:
現(xiàn)代瀏覽器(
Chrome、Edge、Safari等)從安全和用戶體驗(yàn)角度出發(fā),強(qiáng)制要求 “語(yǔ)音合成 / 音頻播放” 必須由用戶主動(dòng)交互觸發(fā)(如點(diǎn)擊、觸摸、鍵盤輸入),模擬點(diǎn)擊會(huì)被認(rèn)為是腳本,沒(méi)有任何技術(shù)手段能完全 “繞過(guò)” 這一限制。
解決辦法: “弱交互觸發(fā)”
頁(yè)面加載完成后,用戶只要進(jìn)行 “極輕微的交互”(如點(diǎn)擊頁(yè)面任意位置、滾動(dòng)鼠標(biāo)滾輪、按任意鍵盤鍵),就會(huì)立即觸發(fā)語(yǔ)音播報(bào)。
② 是否清除了其它正在播放的語(yǔ)音:
if (window.speechSynthesis.speaking) {
window.speechSynthesis.cancel();
}
總結(jié)
到此這篇關(guān)于JS純前端實(shí)現(xiàn)瀏覽器語(yǔ)音播報(bào)、朗讀功能的文章就介紹到這了,更多相關(guān)JS純前端瀏覽器語(yǔ)音播報(bào)朗讀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)移動(dòng)端彈窗后禁止?jié)L動(dòng)
這篇文章主要介紹了JavaScript實(shí)現(xiàn)移動(dòng)端彈窗后禁止?jié)L動(dòng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
微信小程序列表渲染功能之列表下拉刷新及上拉加載的實(shí)現(xiàn)方法分析
js實(shí)現(xiàn)分享到隨頁(yè)面滾動(dòng)而滑動(dòng)效果的方法
javascript獲取select標(biāo)簽選中的值
解決微信小程序中轉(zhuǎn)換時(shí)間格式IOS不兼容的問(wèn)題

