HTML版英语学习系统

发布于:2025-06-09 ⋅ 阅读:(20) ⋅ 点赞:(0)

HTML版英语学习系统

这是一个完全免费、无需安装、功能完整的英语学习工具,使用HTML + CSS +JavaScript实现。

功能

  1. 文本朗读练习 - 输入英文文章,系统朗读帮助练习听力和发音,适合跟读练习,模仿学习;
  2. 实时词典查询 - 双击任意英文单词即可查看释义、音标和发音,适合新词汇学习和发音;(这一点,使用 Free Dictionary API,要联网)
  3. 分段学习 - 可以选择朗读全文、选中段落或当前段落;
  4. 重复练习 - 支持1-3次重复或循环播放,强化记忆;
  5. 学生自学 - 适合学生课文预习和自学;

个性化设置

  1. 语音选择 - 可选择不同的英语发音(美音、英音等)
  2. 语速调节 - 从0.5到2倍速,适应不同学习阶段
  3. 音调控制 - 调整音调让发音更清晰
  4. 音量控制 - 根据环境调整合适音量

运行截图

双击单词情况如下:

源码如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>英语学习系统 - 文本转语音与词典查询</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1000px;
            margin: 0 auto;
            padding: 20px;
            color: #333;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
        }
        
        .container {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            padding: 30px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
        }
        
        h1 {
            color: #2c3e50;
            text-align: center;
            margin-bottom: 10px;
            font-size: 28px;
        }
        
        .subtitle {
            text-align: center;
            color: #666;
            margin-bottom: 30px;
            font-style: italic;
        }
        
        .controls {
            margin: 20px 0;
            background-color: #fff;
            padding: 25px;
            border-radius: 12px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.08);
        }
        
        textarea {
            width: 100%;
            height: 220px;
            margin: 10px 0;
            padding: 20px;
            border: 2px solid #e0e0e0;
            border-radius: 8px;
            font-size: 18px;
            line-height: 1.6;
            resize: vertical;
            box-sizing: border-box;
            transition: border-color 0.3s;
        }
        
        textarea:focus {
            outline: none;
            border-color: #667eea;
            box-shadow: 0 0 10px rgba(102, 126, 234, 0.2);
        }
        
        .parameter {
            display: flex;
            align-items: center;
            margin: 20px 0;
            justify-content: flex-start;
            gap: 15px;
        }
        
        .parameter label {
            min-width: 90px;
            text-align: right;
            margin-right: 15px;
            color: #555;
            font-weight: 600;
        }
        
        select {
            padding: 10px;
            border: 2px solid #e0e0e0;
            border-radius: 6px;
            background-color: #fff;
            font-size: 14px;
            transition: border-color 0.3s;
        }
        
        select:focus {
            outline: none;
            border-color: #667eea;
        }
        
        #voiceSelect {
            width: 460px;
        }
        
        #playCount {
            width: 130px;
        }
        
        input[type="range"] {
            width: 300px;
            height: 8px;
            margin: 0 15px;
            -webkit-appearance: none;
            background: linear-gradient(to right, #667eea, #764ba2);
            border-radius: 4px;
            outline: none;
        }
        
        input[type="range"]::-webkit-slider-thumb {
            -webkit-appearance: none;
            width: 20px;
            height: 20px;
            background: #fff;
            border: 3px solid #667eea;
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 2px 6px rgba(0,0,0,0.2);
        }
        
        .value-display {
            min-width: 40px;
            text-align: center;
            background: #f8f9fa;
            padding: 5px 10px;
            border-radius: 4px;
            font-weight: 600;
            color: #667eea;
        }
        
        .status-display {
            margin: 25px 0;
            padding: 15px 20px;
            background: linear-gradient(135deg, #667eea, #764ba2);
            color: white;
            border-radius: 8px;
            font-size: 15px;
            font-weight: 500;
            text-align: center;
        }
        
        .buttons-container {
            margin-top: 25px;
            display: flex;
            justify-content: center;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        .special-buttons {
            margin-top: 20px;
            padding-top: 20px;
            border-top: 2px solid #f0f0f0;
            display: flex;
            justify-content: center;
            gap: 15px;
            flex-wrap: wrap;
        }
        
        button {
            padding: 12px 25px;
            border: none;
            border-radius: 8px;
            font-size: 15px;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            color: white;
            background: linear-gradient(135deg, #4CAF50, #45a049);
            box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3);
        }
        
        button:hover {
            transform: translateY(-2px);
            box-shadow: 0 6px 20px rgba(76, 175, 80, 0.4);
        }
        
        button:active {
            transform: translateY(0);
        }
        
        .special-button {
            background: linear-gradient(135deg, #2196F3, #1976D2);
            box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3);
        }
        
        .special-button:hover {
            box-shadow: 0 6px 20px rgba(33, 150, 243, 0.4);
        }
        
        /* 词典查询相关样式 */
        .word-popup {
            position: absolute;
            background: white;
            border: 2px solid #667eea;
            border-radius: 12px;
            padding: 25px;
            max-width: 450px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            z-index: 1000;
            font-size: 14px;
            animation: popupShow 0.3s ease;
        }

        @keyframes popupShow {
            from { opacity: 0; transform: scale(0.9) translateY(-10px); }
            to { opacity: 1; transform: scale(1) translateY(0); }
        }

        .word-popup .word-header {
            display: flex;
            align-items: baseline;
            gap: 15px;
            flex-wrap: wrap;
            margin-bottom: 20px;
            border-bottom: 2px solid #f0f0f0;
            padding-bottom: 15px;
        }

        .word-popup h3 {
            margin: 0;
            color: #667eea;
            font-size: 24px;
            font-weight: 700;
        }

        .word-popup .phonetic {
            color: #666;
            font-family: monospace;
            background: #f8f9fa;
            padding: 4px 8px;
            border-radius: 4px;
        }

        .word-popup .audio-btn {
            background: linear-gradient(135deg, #667eea, #764ba2);
            border: none;
            padding: 8px 15px;
            border-radius: 6px;
            cursor: pointer;
            color: white;
            font-size: 13px;
            margin-bottom: 15px;
            transition: all 0.3s;
        }

        .word-popup .audio-btn:hover {
            transform: translateY(-1px);
            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
        }

        .word-popup .meanings {
            max-height: 350px;
            overflow-y: auto;
            margin-bottom: 20px;
        }

        .word-popup .meaning-group {
            margin-bottom: 20px;
            background: #fafbfc;
            padding: 15px;
            border-radius: 8px;
        }

        .word-popup .part-of-speech {
            color: #667eea;
            font-weight: 700;
            margin-bottom: 10px;
            font-size: 16px;
        }

        .word-popup ol {
            margin: 0;
            padding-left: 25px;
            color: #333;
        }

        .word-popup ol li {
            margin: 10px 0;
            line-height: 1.5;
        }

        .word-popup .definition {
            color: #333;
            margin-bottom: 6px;
        }

        .word-popup .example {
            color: #666;
            font-style: italic;
            background: #e8f4fd;
            padding: 8px;
            border-left: 3px solid #667eea;
            margin-top: 8px;
            border-radius: 4px;
        }

        .word-popup .close-btn {
            width: 100%;
            margin-top: 20px;
            padding: 12px;
            background: linear-gradient(135deg, #95a5a6, #7f8c8d);
            border: none;
            border-radius: 6px;
            cursor: pointer;
            color: white;
            font-weight: 600;
        }

        .word-popup .close-btn:hover {
            background: linear-gradient(135deg, #7f8c8d, #6c7b7d);
        }

        .error-message {
            position: fixed;
            bottom: 30px;
            right: 30px;
            background: linear-gradient(135deg, #e74c3c, #c0392b);
            color: white;
            padding: 15px 25px;
            border-radius: 8px;
            animation: fadeIn 0.3s ease;
            box-shadow: 0 6px 20px rgba(231, 76, 60, 0.3);
            font-weight: 600;
        }

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

        .learning-tip {
            background: linear-gradient(135deg, #f093fb, #f5576c);
            color: white;
            padding: 15px;
            border-radius: 8px;
            margin-bottom: 20px;
            <!-- text-align: center; -->
            font-weight: 500;
        }

        @media (max-width: 768px) {
            body {
                padding: 10px;
            }
            
            .container {
                padding: 20px;
            }
            
            .parameter {
                flex-direction: column;
                align-items: flex-start;
            }
            
            .parameter label {
                text-align: left;
                margin-bottom: 8px;
            }
            
            #voiceSelect {
                width: 100%;
            }
            
            input[type="range"] {
                width: 100%;
                margin: 10px 0;
            }
            
            .buttons-container,
            .special-buttons {
                flex-direction: column;
                align-items: center;
            }
            
            button {
                width: 200px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>英语学习系统</h1>
        <p class="subtitle">文本转语音 + 实时词典查询</p>
        
        <div class="learning-tip">
            💡 (1)如果“开始朗读”不发音,请考虑选择语音是否不当。(2)可以设置播放次数,调整语速、音量和音调。(2)双击英文单词可查看英文释义、音标(这项功能需要联网,使用 Free Dictionary API实现)。
        </div>
        
        <div class="controls">
            <label>你可以在下面文本区域输入文字,或者往其中粘贴复制的文本:</label>
            <textarea id="textToSpeak" placeholder="请输入英文文本开始学习...">Welcome to the English Learning System!
This is an interactive text-to-speech application with dictionary lookup.
You can practice pronunciation, adjust speech parameters, and learn new vocabulary.
Double-click on any word like "pronunciation" or "vocabulary" to see its definition and hear how it sounds.</textarea>
            
            <div class="parameter">
                <label for="voiceSelect">语音:</label>
                <select id="voiceSelect"></select>

                <label for="playCount">次数:</label>
                <select id="playCount">
                    <option value="1">播放1次</option>
                    <option value="2">播放2次</option>
                    <option value="3">播放3次</option>
                    <option value="-1">循环播放</option>
                </select>
            </div>
            
            <div class="parameter">
                <label for="rate">语速:</label>
                <input type="range" id="rate" min="0.5" max="2" step="0.1" value="1">
                <span id="rateValue" class="value-display">1.0</span>

                <label for="pitch">音调:</label>
                <input type="range" id="pitch" min="0.5" max="2" step="0.1" value="1">
                <span id="pitchValue" class="value-display">1.0</span>
            </div>
            
            <div class="parameter">            
                <label for="volume">音量:</label>
                <input type="range" id="volume" min="0" max="1" step="0.1" value="1">
                <span id="volumeValue" class="value-display">1.0</span>
            </div>
            
            <div class="status-display" id="statusDisplay">系统就绪 - 开始你的英语学习之旅!</div>
            
            <div class="buttons-container">
                <button onclick="speak()">🔊 开始朗读</button>
                <button onclick="pause()">⏸️ 暂停</button>
                <button onclick="resume()">▶️ 继续</button>
                <button onclick="stop()">⏹️ 停止</button>

                <button class="special-button" onclick="speakSelectedText()">📖 读选中文本</button>
                <button class="special-button" onclick="speakCurrentParagraph()">📄 读当前段落</button>
            </div>
        </div>
    </div>

    <script>
        let speechSynth = window.speechSynthesis;
        let utterance = null;
        let currentVoice = null;
        let voices = [];
        let playCount = 1;
        let totalPlayCount = 1;

        // 加载语音列表
        function loadVoices() {
            voices = speechSynth.getVoices();
            let voiceSelect = document.getElementById('voiceSelect');
            
            // 对语音进行排序
            voices.sort((a, b) => {
                if (a.lang < b.lang) return -1;
                if (a.lang > b.lang) return 1;
                return a.name.localeCompare(b.name);
            });

            // 创建语言分组的对象
            let voicesByLang = {};
            voices.forEach(voice => {
                if (!voicesByLang[voice.lang]) {
                    voicesByLang[voice.lang] = [];
                }
                voicesByLang[voice.lang].push(voice);
            });

            voiceSelect.innerHTML = '';
            
            // 遍历排序后的语言分组
            Object.keys(voicesByLang).sort().forEach(lang => {
                let groupElement = document.createElement('optgroup');
                groupElement.label = getLangLabel(lang);

                voicesByLang[lang].forEach((voice) => {
                    let option = document.createElement('option');
                    option.textContent = `${voice.name}`;
                    option.setAttribute('data-voice-index', voices.indexOf(voice));
                    
                    if (currentVoice && voice.name === currentVoice.name) {
                        option.selected = true;
                    }
                    
                    groupElement.appendChild(option);
                });

                voiceSelect.appendChild(groupElement);
            });

            // 初始化当前语音
            if (!currentVoice && voices.length > 0) {
                currentVoice = voices[0];
                voiceSelect.selectedIndex = 0;
            }
        }

        // 添加双击事件监听
        document.getElementById('textToSpeak').addEventListener('dblclick', function(e) {
            const selectedText = window.getSelection().toString().trim();
            if (selectedText && /^[a-zA-Z]+$/.test(selectedText)) {
                lookupWord(selectedText);
            }
        });

        // 查询单词
        async function lookupWord(word) {
            updateStatus(`正在查询单词 "${word}"...`);
            try {
                const response = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`);
                const data = await response.json();
                
                if (data && data.length > 0) {
                    showWordDefinition(word, data[0]);
                    updateStatus(`单词 "${word}" 查询成功!`);
                } else {
                    showError('未找到该单词的释义');
                    updateStatus('查询失败 - 未找到释义');
                }
            } catch (error) {
                showError('查询失败,请稍后重试');
                updateStatus('网络查询失败');
            }
        }

        // 显示单词释义
        function showWordDefinition(word, data) {
            let popup = document.createElement('div');
            popup.className = 'word-popup';
            
            // 构建释义内容
            let content = `<div class="word-header">
                <h3>${word}</h3>
                ${data.phonetics && data.phonetics[0]?.text ? 
                `<span class="phonetic">${data.phonetics[0].text}</span>` : ''}
            </div>`;
            
            // 添加发音按钮(如果有音频链接)
            const audioUrl = data.phonetics?.find(p => p.audio)?.audio;
            if (audioUrl) {
                content += `<button class="audio-btn" onclick="playAudio('${audioUrl}')">
                    🔊 播放发音
                </button>`;
            }
            
            // 添加释义
            if (data.meanings && data.meanings.length > 0) {
                content += '<div class="meanings">';
                data.meanings.forEach(meaning => {
                    content += `
                        <div class="meaning-group">
                            <div class="part-of-speech">${meaning.partOfSpeech}</div>
                            <ol>
                                ${meaning.definitions.map(def => `
                                    <li>
                                        <div class="definition">${def.definition}</div>
                                        ${def.example ? `
                                            <div class="example">Example: ${def.example}</div>
                                        ` : ''}
                                    </li>
                                `).join('')}
                            </ol>
                        </div>
                    `;
                });
                content += '</div>';
            }
            
            // 添加关闭按钮
            content += '<button class="close-btn" onclick="this.parentElement.remove()">关闭</button>';
            
            popup.innerHTML = content;
            document.body.appendChild(popup);
            
            // 定位弹出框
            const selection = window.getSelection();
            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();
            
            // 调整弹出位置,确保在可视区域内
            let left = rect.left + window.scrollX;
            let top = rect.bottom + window.scrollY + 10;
            
            // 检查右边界
            if (left + 450 > window.innerWidth) {
                left = window.innerWidth - 470;
            }
            
            popup.style.left = `${left}px`;
            popup.style.top = `${top}px`;
        }

        // 播放音频
        function playAudio(url) {
            new Audio(url).play();
        }

        // 显示错误信息
        function showError(message) {
            const errorDiv = document.createElement('div');
            errorDiv.className = 'error-message';
            errorDiv.textContent = message;
            document.body.appendChild(errorDiv);
            setTimeout(() => errorDiv.remove(), 3000);
        }

        // 获取语言显示名称
        function getLangLabel(langCode) {
            const langNames = {
                'zh-CN': '中文 (中国)',
                'zh-TW': '中文 (台湾)',
                'zh-HK': '中文 (香港)',
                'en-US': '英语 (美国)',
                'en-GB': '英语 (英国)',
                'ja-JP': '日语 (日本)',
                'ko-KR': '韩语 (韩国)',
                'fr-FR': '法语 (法国)',
                'de-DE': '德语 (德国)',
                'es-ES': '西班牙语 (西班牙)',
                'it-IT': '意大利语 (意大利)',
                'ru-RU': '俄语 (俄罗斯)',
            };
            
            return langNames[langCode] || langCode;
        }

        // 初始化语音
        if (speechSynth.onvoiceschanged !== undefined) {
            speechSynth.onvoiceschanged = loadVoices;
        }
        setTimeout(loadVoices, 100);

        // 监听播放次数选择变化
        document.getElementById('playCount').addEventListener('change', function(e) {
            totalPlayCount = parseInt(e.target.value);
        });

        // 语音选择改变事件
        document.getElementById('voiceSelect').addEventListener('change', function(e) {
            let selectedOption = e.target.options[e.target.selectedIndex];
            let voiceIndex = selectedOption.getAttribute('data-voice-index');
            currentVoice = voices[voiceIndex];
        });

        // 更新滑块值显示
        document.getElementById('rate').addEventListener('input', function(e) {
            document.getElementById('rateValue').textContent = parseFloat(e.target.value).toFixed(1);
        });
        
        document.getElementById('pitch').addEventListener('input', function(e) {
            document.getElementById('pitchValue').textContent = parseFloat(e.target.value).toFixed(1);
        });
        
        document.getElementById('volume').addEventListener('input', function(e) {
            document.getElementById('volumeValue').textContent = parseFloat(e.target.value).toFixed(1);
        });

        // 更新状态显示
        function updateStatus(message) {
            document.getElementById('statusDisplay').textContent = message;
        }

        // 创建并播放音频
        function createAndPlayUtterance(text) {
            if (!text.trim()) {
                updateStatus('没有文本可以朗读');
                return;
            }

            utterance = new SpeechSynthesisUtterance(text);
            
            if (currentVoice) {
                utterance.voice = currentVoice;
            }
            
            utterance.rate = parseFloat(document.getElementById('rate').value);
            utterance.pitch = parseFloat(document.getElementById('pitch').value);
            utterance.volume = parseFloat(document.getElementById('volume').value);

            utterance.onend = function(event) {
                console.log(`第 ${playCount} 次播放结束`);
                
                if (totalPlayCount === -1 || playCount < totalPlayCount) {
                    playCount++;
                    updateStatus(`正在播放第 ${playCount} 次...`);
                    setTimeout(() => createAndPlayUtterance(text), 500);
                } else {
                    updateStatus('播放完成 ✓');
                }
            };

            utterance.onstart = function() {
                updateStatus(`正在播放第 ${playCount} 次... 🔊`);
            };

            speechSynth.speak(utterance);
        }

        // 开始朗读
        function speak() {
            stop();
            let text = document.getElementById('textToSpeak').value;
            playCount = 1;
            totalPlayCount = parseInt(document.getElementById('playCount').value);
            createAndPlayUtterance(text);
        }

        // 获取选中的文本
        function getSelectedText() {
            const textarea = document.getElementById('textToSpeak');
            const start = textarea.selectionStart;
            const end = textarea.selectionEnd;
            
            if (start === end) {
                return '';
            }
            
            return textarea.value.substring(start, end);
        }

        // 获取光标所在段落的文本
        function getCurrentParagraph() {
            const textarea = document.getElementById('textToSpeak');
            const text = textarea.value;
            const cursorPosition = textarea.selectionStart;
            
            // 将文本按换行符分割
            const paragraphs = text.split('\n');
            
            let currentPosition = 0;
            for (let i = 0; i < paragraphs.length; i++) {
                const paragraphLength = paragraphs[i].length + 1; // +1 是为了计入换行符
                const paragraphEnd = currentPosition + paragraphLength;
                
                if (cursorPosition <= paragraphEnd) {
                    return paragraphs[i].trim();
                }
                
                currentPosition = paragraphEnd;
            }
            
            return paragraphs[paragraphs.length - 1].trim();
        }

        // 朗读选中的文本
        function speakSelectedText() {
            stop();
            const selectedText = getSelectedText();
            
            if (!selectedText) {
                updateStatus('请先选中要朗读的文本');
                return;
            }
            
            playCount = 1;
            totalPlayCount = parseInt(document.getElementById('playCount').value);
            updateStatus('开始朗读选中文本...');
            createAndPlayUtterance(selectedText);
        }

        // 朗读光标所在段落
        function speakCurrentParagraph() {
            stop();
            const currentParagraph = getCurrentParagraph();
            
            if (!currentParagraph) {
                updateStatus('光标所在位置没有找到有效段落');
                return;
            }
            
            playCount = 1;
            totalPlayCount = parseInt(document.getElementById('playCount').value);
            updateStatus('开始朗读当前段落...');
            createAndPlayUtterance(currentParagraph);
        }

        // 暂停朗读
        function pause() {
            speechSynth.pause();
            updateStatus('已暂停 ⏸️');
        }

        // 继续朗读
        function resume() {
            speechSynth.resume();
            updateStatus('继续播放... ▶️');
        }

        // 停止朗读
        function stop() {
            speechSynth.cancel();
            playCount = totalPlayCount;
            updateStatus('已停止 ⏹️');
        }
    </script>
</body>
</html>

OK!


网站公告

今日签到

点亮在社区的每一天
去签到