欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于JavaScript+HTML實(shí)現(xiàn)文章逐句高亮朗讀功能

 更新時(shí)間:2025年06月04日 10:35:55   作者:技術(shù)小丁  
在這個(gè)信息爆炸的時(shí)代,我們每天都要面對(duì)大量的文字閱讀,無(wú)論是學(xué)習(xí)、工作還是個(gè)人成長(zhǎng),閱讀都扮演著至關(guān)重要的角色,然而,在快節(jié)奏的生活中,我們往往難以找到足夠的安靜時(shí)間專注于閱讀,本文用HTML+JavaScript實(shí)現(xiàn)了一個(gè)基于Web的語(yǔ)音文章朗讀器,需要的朋友可以參考下

效果演示

項(xiàng)目核心

本項(xiàng)目主要包含以下核心功能:

  • 語(yǔ)音合成(Text-to-Speech)功能
  • 控制播放、暫停、繼續(xù)和停止操作
  • 語(yǔ)音選擇功能
  • 閱讀進(jìn)度保存與恢復(fù)
  • 句子級(jí)高亮顯示
  • 點(diǎn)擊任意句子直接跳轉(zhuǎn)并朗讀

頁(yè)面結(jié)構(gòu)

控制區(qū)域

包含所有操作按鈕(開(kāi)始、暫停、繼續(xù)、停止、重置)和語(yǔ)音選擇下拉框。

<div class="controls">
    <button id="playBtn">開(kāi)始朗讀</button>
    <button id="pauseBtn" disabled>暫停</button>
    <button id="resumeBtn" disabled>繼續(xù)</button>
    <button id="stopBtn" disabled>停止</button>
    <select id="voiceSelect" class="voice-select"></select>
    <button id="resetBtn">重置進(jìn)度</button>
</div>

文章區(qū)域

包含多個(gè)段落,每個(gè)段落由多個(gè)可交互的句子組成。

<div class="article" id="article">
    <p class="paragraph">
        <span class="sentence">在編程的世界里,學(xué)習(xí)是一個(gè)永無(wú)止境的過(guò)程。</span>
        <span class="sentence">隨著技術(shù)的不斷發(fā)展,我們需要不斷更新自己的知識(shí)和技能。</span>
        <span class="sentence">HTML、CSS和JavaScript是構(gòu)建現(xiàn)代網(wǎng)頁(yè)的三大基石。</span>
    </p>
    <p class="paragraph">
        <span class="sentence">掌握這些基礎(chǔ)技術(shù)后,你可以進(jìn)一步學(xué)習(xí)各種前端框架和工具。</span>
        <span class="sentence">React、Vue和Angular是目前最流行的前端框架。</span>
        <span class="sentence">它們都采用了組件化的開(kāi)發(fā)模式,提高了代碼的可維護(hù)性和復(fù)用性。</span>
    </p>
    <p class="paragraph">
        <span class="sentence">除了前端技術(shù),后端開(kāi)發(fā)也是全棧工程師必須掌握的技能。</span>
        <span class="sentence">Node.js讓JavaScript可以用于服務(wù)器端編程,大大擴(kuò)展了JavaScript的應(yīng)用范圍。</span>
        <span class="sentence">數(shù)據(jù)庫(kù)技術(shù)也是開(kāi)發(fā)中的重要組成部分。</span>
    </p>
</div>

進(jìn)度信息

顯示當(dāng)前閱讀進(jìn)度。

<div class="progress-info">
    當(dāng)前進(jìn)度: <span id="progressText">0/0</span>
    <div class="progress-bar-container">
        <div class="progress-bar"></div>
    </div>
</div>

核心功能實(shí)現(xiàn)

定義基礎(chǔ)變量

獲取DOM元素

const sentences = document.querySelectorAll('.sentence');
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
const resumeBtn = document.getElementById('resumeBtn');
const stopBtn = document.getElementById('stopBtn');
const resetBtn = document.getElementById('resetBtn');
const voiceSelect = document.getElementById('voiceSelect');
const progressText = document.getElementById('progressText');
const progressBar = document.querySelector('.progress-bar');

定義語(yǔ)音合成相關(guān)變量

let speechSynthesis = window.speechSynthesis;
let voices = [];
let currentUtterance = null;
let currentSentenceIndex = 0;
let isPaused = false;

語(yǔ)音合成初始化

通過(guò) window.speechSynthesis API 獲取系統(tǒng)支持的語(yǔ)音列表,并填充到下拉選擇框中。

function initSpeechSynthesis() {
    // 獲取可用的語(yǔ)音列表
    voices = speechSynthesis.getVoices();

    // 填充語(yǔ)音選擇下拉框
    voiceSelect.innerHTML = '';
    voices.forEach((voice, index) => {
        const option = document.createElement('option');
        option.value = index;
        option.textContent = `${voice.name} (${voice.lang})`;
        voiceSelect.appendChild(option);
    });

    // 嘗試選擇中文語(yǔ)音
    const chineseVoice = voices.find(voice =>{
        voice.lang.includes('zh') || voice.lang.includes('cmn')
    });
    if (chineseVoice) {
        const voiceIndex = voices.indexOf(chineseVoice);
        voiceSelect.value = voiceIndex;
    }
}

句子朗讀功能

function speakSentence(index) {
    if (index >= sentences.length || index < 0) return;
    // 停止當(dāng)前朗讀
    if (currentUtterance) {
        speechSynthesis.cancel();
    }
    // 更新當(dāng)前句子高亮
    updateHighlight(index);
    // 創(chuàng)建新的語(yǔ)音合成實(shí)例
    const selectedVoiceIndex = voiceSelect.value;
    const utterance = new SpeechSynthesisUtterance(sentences[index].textContent);
    if (voices[selectedVoiceIndex]) {
        utterance.voice = voices[selectedVoiceIndex];
    }
    utterance.rate = 0.9; // 稍微慢一點(diǎn)的語(yǔ)速
    // 朗讀開(kāi)始時(shí)的處理
    utterance.onstart = function() {
        sentences[index].classList.add('reading');
        playBtn.disabled = true;
        pauseBtn.disabled = false;
        resumeBtn.disabled = true;
        stopBtn.disabled = false;
    };
    // 朗讀結(jié)束時(shí)的處理
    utterance.onend = function() {
        sentences[index].classList.remove('reading');
        if (!isPaused) {
            if (currentSentenceIndex >= sentences.length - 1) {
                // 朗讀完成
                playBtn.disabled = false;
                pauseBtn.disabled = true;
                resumeBtn.disabled = true;
                stopBtn.disabled = true;
                updateProgressText();
                return;
            }
            currentSentenceIndex++;
            saveProgress();
            speakSentence(currentSentenceIndex);
        }
    };
    // 開(kāi)始朗讀
    currentUtterance = utterance;
    speechSynthesis.speak(utterance);
    updateProgressText();
}

句子高亮功能

function updateHighlight(index) {
    sentences.forEach((sentence, i) => {
        sentence.classList.remove('current');
        if (i === index) {
            sentence.classList.add('current');
            // 滾動(dòng)到當(dāng)前句子
            sentence.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
    });
}

更新進(jìn)度文本

function updateProgressText() {
    progressText.textContent = `${currentSentenceIndex + 1}/${sentences.length}`;
    const percentage = (currentSentenceIndex + 1) / sentences.length * 100;
    progressBar.style.width = `${percentage}%`;
}

進(jìn)度保存與恢復(fù)

保存進(jìn)度到本地存儲(chǔ)

function saveProgress() {
    localStorage.setItem('readingProgress', currentSentenceIndex);
    localStorage.setItem('articleId', 'demoArticle'); 
    updateProgressText();
}

從本地存儲(chǔ)加載進(jìn)度

function loadProgress() {
    const savedArticleId = localStorage.getItem('articleId');
    if (savedArticleId === 'demoArticle') {
        const savedProgress = localStorage.getItem('readingProgress');
        if (savedProgress !== null) {
            currentSentenceIndex = parseInt(savedProgress);
            if (currentSentenceIndex >= sentences.length) {
                currentSentenceIndex = 0;
            }
            updateHighlight(currentSentenceIndex);
            updateProgressText();
        }
    }
}

點(diǎn)擊句子朗讀跳轉(zhuǎn)功能

sentences.forEach((sentence, index) => {
    sentence.addEventListener('click', function() {
        currentSentenceIndex = index;
        speakSentence(currentSentenceIndex);
    });
});

擴(kuò)展建議

  • 語(yǔ)速調(diào)節(jié):增加語(yǔ)速調(diào)節(jié)滑塊,讓用戶自定義朗讀速
  • 多語(yǔ)言支持:自動(dòng)檢測(cè)文本語(yǔ)言并選擇合適的語(yǔ)音引擎
  • 斷句優(yōu)化:改進(jìn)自然語(yǔ)言處理邏輯,使朗讀更符合口語(yǔ)習(xí)慣
  • 多文章支持:擴(kuò)展文章管理系統(tǒng),允許用戶選擇不同文章進(jìn)行朗讀

完整代碼

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文章逐句高亮朗讀</title>
    <style>
        body {
            font-family: 'Microsoft YaHei', sans-serif;
            line-height: 1.6;
            max-width: 800px;
            margin: 0 auto;
            padding: 40px 20px;
            color: #333;
            height: 100vh;
            box-sizing: border-box;
            background: linear-gradient(to bottom right, #f8f9fa, #e9ecef);
        }

        h1 {
            text-align: center;
            color: #2c3e50;
            margin-bottom: 40px;
            font-size: 2.5em;
            letter-spacing: 2px;
            position: relative;
            animation: fadeInDown 1s ease-out forwards;
        }

        @keyframes fadeInDown {
            from {
                opacity: 0;
                transform: translateY(-30px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        h1::after {
            content: '';
            display: block;
            width: 100px;
            height: 4px;
            background: linear-gradient(to right, #3498db, #2980b9);
            margin: 15px auto 0;
            border-radius: 2px;
            animation: growLine 1s ease-out forwards;
        }

        @keyframes growLine {
            from {
                width: 0;
            }
            to {
                width: 100px;
            }
        }
        .controls {
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
            border-radius: 10px;
            padding: 20px;
            background-color: #ffffffcc;
            display: flex;
            flex-direction: column;
            gap: 15px;
            margin-bottom: 30px;
        }
        .controls > div {
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
            gap: 15px;
        }

        button {
            padding: 10px 20px;
            background: linear-gradient(135deg, #3498db, #2980b9);
            color: white;
            border: none;
            border-radius: 25px;
            cursor: pointer;
            font-size: 16px;
            transition: all 0.3s ease-in-out;
            box-shadow: 0 4px 6px rgba(52, 152, 219, 0.3);
        }

        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 12px rgba(52, 152, 219, 0.4);
        }

        button:disabled {
            background: linear-gradient(135deg, #95a5a6, #7f8c8d);
            box-shadow: none;
            transform: none;
        }

        .article {
            font-size: 18px;
            line-height: 1.8;
            background-color: #ffffffee;
            border-radius: 10px;
            padding: 25px;
            margin-top: 30px;
            box-shadow: 0 8px 20px rgba(0, 0, 0, 0.05);
            margin-bottom: 30px;
            position: relative;
            z-index: 0
        }
        .article::before {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: radial-gradient(circle at top left, rgba(52, 152, 219, 0.05) 0%, transparent 100%);
            z-index: -1;
            border-radius: 10px;
        }
        .paragraph {
            margin-bottom: 20px;
        }

        .sentence {
            border-radius: 3px;
            transition: all 0.3s ease-in-out;
            cursor: pointer;
            position: relative;
            z-index: 1;
        }

        .sentence:hover {
            background-color: #f0f0f0;
        }
        .sentence::after {
            content: '';
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(255, 255, 255, 0.3);
            opacity: 0;
            z-index: -1;
            transition: opacity 0.3s ease-in-out;
        }
        .sentence:hover::after {
            opacity: 1;
        }
        .current {
            background-color: #fffde7 !important;
            font-weight: bold;
            transform: scale(1.05);
            box-shadow: 0 2px 8px rgba(255, 221, 0, 0.3);
        }
        .progress-info {
            text-align: center;
            margin-top: 20px;
            font-size: 14px;
            color: #7f8c8d;
        }

        select {
            padding: 8px;
            border-radius: 4px;
            border: 1px solid #bdc3c7;
            font-size: 16px;
        }

        .voice-select {
            min-width: 220px;
            padding: 10px 12px;
            border-radius: 25px;
            border: 1px solid #bdc3c7;
            font-size: 16px;
            background-color: #f8f9fa;
            transition: all 0.3s ease-in-out;
            appearance: none;
            background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24'%3E%3Cpath fill='%23555' d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
            background-repeat: no-repeat;
            background-position: right 15px center;
            background-size: 12px;
            display: block;
            margin: 0 auto;
        }

        .voice-select:focus {
            outline: none;
            border-color: #3498db;
            box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
        }

        .progress-info {
            text-align: center;
            margin-top: 30px;
            font-size: 14px;
            color: #7f8c8d;
            position: relative;
            height: 30px;
        }

        .progress-bar-container {
            width: 100%;
            height: 6px;
            background-color: #ecf0f1;
            border-radius: 3px;
            overflow: hidden;
            margin: 10px 0;
        }

        .progress-bar {
            height: 100%;
            width: 0;
            background: linear-gradient(to right, #3498db, #2980b9);
            transition: width 0.3s ease-in-out;
        }
    </style>
</head>
<body>
<h1>文章逐句高亮朗讀</h1>

<div class="controls">
    <div>
        <button id="playBtn">開(kāi)始朗讀</button>
        <button id="pauseBtn" disabled>暫停</button>
        <button id="resumeBtn" disabled>繼續(xù)</button>
        <button id="stopBtn" disabled>停止</button>
        <button id="resetBtn">重置進(jìn)度</button>
    </div>
    <select id="voiceSelect" class="voice-select"></select>
</div>

<div class="article" id="article">
    <p class="paragraph">
        <span class="sentence">在編程的世界里,學(xué)習(xí)是一個(gè)永無(wú)止境的過(guò)程。</span>
        <span class="sentence">隨著技術(shù)的不斷發(fā)展,我們需要不斷更新自己的知識(shí)和技能。</span>
        <span class="sentence">HTML、CSS和JavaScript是構(gòu)建現(xiàn)代網(wǎng)頁(yè)的三大基石。</span>
    </p>
    <p class="paragraph">
        <span class="sentence">掌握這些基礎(chǔ)技術(shù)后,你可以進(jìn)一步學(xué)習(xí)各種前端框架和工具。</span>
        <span class="sentence">React、Vue和Angular是目前最流行的前端框架。</span>
        <span class="sentence">它們都采用了組件化的開(kāi)發(fā)模式,提高了代碼的可維護(hù)性和復(fù)用性。</span>
    </p>
    <p class="paragraph">
        <span class="sentence">除了前端技術(shù),后端開(kāi)發(fā)也是全棧工程師必須掌握的技能。</span>
        <span class="sentence">Node.js讓JavaScript可以用于服務(wù)器端編程,大大擴(kuò)展了JavaScript的應(yīng)用范圍。</span>
        <span class="sentence">數(shù)據(jù)庫(kù)技術(shù)也是開(kāi)發(fā)中的重要組成部分。</span>
    </p>
</div>

<div class="progress-info">
    當(dāng)前進(jìn)度: <span id="progressText">0/0</span>
    <div class="progress-bar-container">
        <div class="progress-bar"></div>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        // 獲取DOM元素
        const sentences = document.querySelectorAll('.sentence');
        const playBtn = document.getElementById('playBtn');
        const pauseBtn = document.getElementById('pauseBtn');
        const resumeBtn = document.getElementById('resumeBtn');
        const stopBtn = document.getElementById('stopBtn');
        const resetBtn = document.getElementById('resetBtn');
        const voiceSelect = document.getElementById('voiceSelect');
        const progressText = document.getElementById('progressText');
        const progressBar = document.querySelector('.progress-bar');

        // 語(yǔ)音合成相關(guān)變量
        let speechSynthesis = window.speechSynthesis;
        let voices = [];
        let currentUtterance = null;
        let currentSentenceIndex = 0;
        let isPaused = false;

        // 從本地存儲(chǔ)加載進(jìn)度
        loadProgress();

        // 初始化語(yǔ)音合成
        function initSpeechSynthesis() {
            // 獲取可用的語(yǔ)音列表
            voices = speechSynthesis.getVoices();

            // 填充語(yǔ)音選擇下拉框
            voiceSelect.innerHTML = '';
            voices.forEach((voice, index) => {
                const option = document.createElement('option');
                option.value = index;
                option.textContent = `${voice.name} (${voice.lang})`;
                voiceSelect.appendChild(option);
            });

            // 嘗試選擇中文語(yǔ)音
            const chineseVoice = voices.find(voice =>{
                voice.lang.includes('zh') || voice.lang.includes('cmn')
            });
            if (chineseVoice) {
                const voiceIndex = voices.indexOf(chineseVoice);
                voiceSelect.value = voiceIndex;
            }
        }

        // 語(yǔ)音列表加載可能需要時(shí)間
        speechSynthesis.onvoiceschanged = initSpeechSynthesis;
        initSpeechSynthesis();

        // 朗讀指定句子
        function speakSentence(index) {
            if (index >= sentences.length || index < 0) return;
            // 停止當(dāng)前朗讀
            if (currentUtterance) {
                speechSynthesis.cancel();
            }
            // 更新當(dāng)前句子高亮
            updateHighlight(index);
            // 創(chuàng)建新的語(yǔ)音合成實(shí)例
            const selectedVoiceIndex = voiceSelect.value;
            const utterance = new SpeechSynthesisUtterance(sentences[index].textContent);
            if (voices[selectedVoiceIndex]) {
                utterance.voice = voices[selectedVoiceIndex];
            }
            utterance.rate = 0.9; // 稍微慢一點(diǎn)的語(yǔ)速
            // 朗讀開(kāi)始時(shí)的處理
            utterance.onstart = function() {
                sentences[index].classList.add('reading');
                playBtn.disabled = true;
                pauseBtn.disabled = false;
                resumeBtn.disabled = true;
                stopBtn.disabled = false;
            };
            // 朗讀結(jié)束時(shí)的處理
            utterance.onend = function() {
                sentences[index].classList.remove('reading');
                if (!isPaused) {
                    if (currentSentenceIndex >= sentences.length - 1) {
                        // 朗讀完成
                        playBtn.disabled = false;
                        pauseBtn.disabled = true;
                        resumeBtn.disabled = true;
                        stopBtn.disabled = true;
                        updateProgressText();
                        return;
                    }
                    currentSentenceIndex++;
                    saveProgress();
                    speakSentence(currentSentenceIndex);
                }
            };
            // 開(kāi)始朗讀
            currentUtterance = utterance;
            speechSynthesis.speak(utterance);
            updateProgressText();
        }

        // 更新句子高亮
        function updateHighlight(index) {
            sentences.forEach((sentence, i) => {
                sentence.classList.remove('current');
                if (i === index) {
                    sentence.classList.add('current');
                    // 滾動(dòng)到當(dāng)前句子
                    sentence.scrollIntoView({ behavior: 'smooth', block: 'center' });
                }
            });
        }

        // 更新進(jìn)度文本
        function updateProgressText() {
            progressText.textContent = `${currentSentenceIndex + 1}/${sentences.length}`;
            const percentage = (currentSentenceIndex + 1) / sentences.length * 100;
            progressBar.style.width = `${percentage}%`;
        }

        // 保存進(jìn)度到本地存儲(chǔ)
        function saveProgress() {
            localStorage.setItem('readingProgress', currentSentenceIndex);
            localStorage.setItem('articleId', 'demoArticle'); // 在實(shí)際應(yīng)用中可以使用文章ID
            updateProgressText();
        }

        // 從本地存儲(chǔ)加載進(jìn)度
        function loadProgress() {
            const savedArticleId = localStorage.getItem('articleId');
            if (savedArticleId === 'demoArticle') {
                const savedProgress = localStorage.getItem('readingProgress');
                if (savedProgress !== null) {
                    currentSentenceIndex = parseInt(savedProgress);
                    if (currentSentenceIndex >= sentences.length) {
                        currentSentenceIndex = 0;
                    }
                    updateHighlight(currentSentenceIndex);
                    updateProgressText();
                }
            }
        }

        // 事件監(jiān)聽(tīng)器
        playBtn.addEventListener('click', function() {
            currentSentenceIndex = 0;
            speakSentence(currentSentenceIndex);
        });

        pauseBtn.addEventListener('click', function() {
            if (speechSynthesis.speaking && !isPaused) {
                speechSynthesis.pause();
                isPaused = true;
                pauseBtn.disabled = true;
                resumeBtn.disabled = false;
            }
        });

        resumeBtn.addEventListener('click', function() {
            if (isPaused) {
                speechSynthesis.resume();
                isPaused = false;
                pauseBtn.disabled = false;
                resumeBtn.disabled = true;
            }
        });

        stopBtn.addEventListener('click', function() {
            speechSynthesis.cancel();
            isPaused = false;
            playBtn.disabled = false;
            pauseBtn.disabled = true;
            resumeBtn.disabled = true;
            stopBtn.disabled = true;

            // 移除所有朗讀樣式
            sentences.forEach(sentence => {
                sentence.classList.remove('reading');
            });
        });

        resetBtn.addEventListener('click', function() {
            localStorage.removeItem('readingProgress');
            localStorage.removeItem('articleId');
            currentSentenceIndex = 0;
            updateHighlight(currentSentenceIndex);
            updateProgressText();
        });

        // 點(diǎn)擊句子跳轉(zhuǎn)到該句子并朗讀
        sentences.forEach((sentence, index) => {
            sentence.addEventListener('click', function() {
                currentSentenceIndex = index;
                speakSentence(currentSentenceIndex);
            });
        });
    });
</script>
</body>
</html>

到此這篇關(guān)于基于JavaScript+HTML實(shí)現(xiàn)文章逐句高亮朗讀功能的文章就介紹到這了,更多相關(guān)JavaScript HTML文章高亮朗讀內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論