HTML版英语学习系统
这是一个完全免费、无需安装、功能完整的英语学习工具,使用HTML + CSS +JavaScript实现。
功能
- 文本朗读练习 - 输入英文文章,系统朗读帮助练习听力和发音,适合跟读练习,模仿学习;
- 实时词典查询 - 双击任意英文单词即可查看释义、音标和发音,适合新词汇学习和发音;(这一点,使用 Free Dictionary API,要联网)
- 分段学习 - 可以选择朗读全文、选中段落或当前段落;
- 重复练习 - 支持1-3次重复或循环播放,强化记忆;
- 学生自学 - 适合学生课文预习和自学;
个性化设置
- 语音选择 - 可选择不同的英语发音(美音、英音等)
- 语速调节 - 从0.5到2倍速,适应不同学习阶段
- 音调控制 - 调整音调让发音更清晰
- 音量控制 - 根据环境调整合适音量
运行截图
双击单词情况如下:
源码如下:
<!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!