/**
* 交互式英语学习应用 - 主要逻辑
* 支持点读、翻译显示和音频播放功能
*/
class InteractiveLearningApp {
constructor() {
this.bookData = null;
this.currentPageIndex = 0;
this.currentAudio = null;
this.isPlaying = false;
this.showTranslation = false;
this.showInteractiveAreas = false;
this.bookmarks = [];
this.searchResults = [];
this.debugMode = false; // 调试模式
this.settings = {
autoTranslation: false,
playbackSpeed: 1,
autoPlayNext: false
};
this.init();
}
async init() {
try {
await this.loadBookData();
this.setupEventListeners();
this.loadSettings();
this.loadBookmarks();
this.renderCurrentPage();
this.updateUI();
} catch (error) {
console.error('应用初始化失败:', error);
this.showToast('应用初始化失败,请刷新页面重试', 'error');
}
}
async loadBookData() {
try {
const response = await fetch('./book_10242.json');
if (!response.ok) {
throw new Error('数据文件加载失败');
}
const jsonData = await response.json();
if (!jsonData.Data) {
throw new Error('数据格式不正确');
}
this.bookData = JSON.parse(jsonData.Data);
console.log('书籍数据加载成功:', this.bookData.length, '页');
} catch (error) {
console.error('数据加载失败:', error);
throw error;
}
}
setupEventListeners() {
// 页面导航
document.getElementById('prevBtn').addEventListener('click', () => this.previousPage());
document.getElementById('nextBtn').addEventListener('click', () => this.nextPage());
// 控制按钮
document.getElementById('translationToggle').addEventListener('click', () => this.toggleTranslation());
document.getElementById('interactiveToggle').addEventListener('click', () => this.toggleInteractiveAreas());
document.getElementById('playAllBtn').addEventListener('click', () => this.playAllPieces());
document.getElementById('bookmarkBtn').addEventListener('click', () => this.toggleBookmark());
document.getElementById('searchBtn').addEventListener('click', () => this.showSearch());
document.getElementById('settingsBtn').addEventListener('click', () => this.showSettings());
// 音频控制
document.getElementById('playPauseBtn').addEventListener('click', () => this.togglePlayPause());
document.getElementById('repeatBtn').addEventListener('click', () => this.repeatAudio());
document.getElementById('audioTimeline').addEventListener('click', (e) => this.seekAudio(e));
// 设置面板
document.getElementById('closeSettings').addEventListener('click', () => this.hideSettings());
// 搜索面板
document.getElementById('closeSearch').addEventListener('click', () => this.hideSearch());
document.getElementById('doSearch').addEventListener('click', () => this.performSearch());
document.getElementById('searchInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.performSearch();
});
// 书签面板
document.getElementById('closeBookmark').addEventListener('click', () => this.hideBookmarks());
document.getElementById('autoTranslation').addEventListener('change', (e) => {
this.settings.autoTranslation = e.target.checked;
this.saveSettings();
});
document.getElementById('playbackSpeed').addEventListener('change', (e) => {
this.settings.playbackSpeed = parseFloat(e.target.value);
this.saveSettings();
if (this.currentAudio) {
this.currentAudio.playbackRate = this.settings.playbackSpeed;
}
});
document.getElementById('autoPlayNext').addEventListener('change', (e) => {
this.settings.autoPlayNext = e.target.checked;
this.saveSettings();
});
// 键盘快捷键
document.addEventListener('keydown', (e) => this.handleKeyPress(e));
// 点击设置面板外部关闭
document.getElementById('settingsPanel').addEventListener('click', (e) => {
if (e.target.id === 'settingsPanel') {
this.hideSettings();
}
});
// 音频事件监听
const audio = document.getElementById('audio');
audio.addEventListener('loadedmetadata', () => this.updateAudioUI());
audio.addEventListener('timeupdate', () => this.updateAudioProgress());
audio.addEventListener('ended', () => this.onAudioEnded());
audio.addEventListener('error', (e) => {
console.error('音频播放失败:', e);
this.showToast('音频播放失败', 'error');
});
}
renderCurrentPage() {
if (!this.bookData || this.currentPageIndex >= this.bookData.length) {
return;
}
const page = this.bookData[this.currentPageIndex];
const pageContainer = document.getElementById('pageContent');
const loading = document.getElementById('loading');
// 显示加载状态
loading.style.display = 'block';
pageContainer.style.display = 'none';
// 加载页面图片
const pageImage = document.getElementById('pageImage');
pageImage.onload = () => {
loading.style.display = 'none';
pageContainer.style.display = 'block';
this.renderTextPieces(page);
};
pageImage.onerror = () => {
// 如果原始图片加载失败,尝试加载加密图片
if (pageImage.src === page.originImgUrl && page.encryptImgUrl) {
pageImage.src = page.encryptImgUrl;
} else {
loading.style.display = 'none';
this.showToast('页面图片加载失败', 'error');
}
};
pageImage.src = page.originImgUrl;
pageImage.alt = `第${page.pageNumber}页`;
}
renderTextPieces(page) {
const textOverlays = document.getElementById('textOverlays');
const pageImage = document.getElementById('pageImage');
// 清空现有的文本片段
textOverlays.innerHTML = '';
// 等待图片完全加载后渲染文本片段
const renderPieces = () => {
// 获取图片的实际位置和尺寸
const imageRect = pageImage.getBoundingClientRect();
const overlayRect = textOverlays.getBoundingClientRect();
// 计算图片相对于overlay容器的偏移
const offsetX = imageRect.left - overlayRect.left;
const offsetY = imageRect.top - overlayRect.top;
// 获取图片的实际显示尺寸
const imageWidth = imageRect.width;
const imageHeight = imageRect.height;
console.log('图片信息:', {
imageWidth,
imageHeight,
offsetX,
offsetY,
debugMode: this.debugMode
});
page.pieces.forEach((piece, index) => {
const textPiece = document.createElement('div');
textPiece.className = 'text-piece';
textPiece.dataset.pieceIndex = index;
// 在调试模式下添加特殊样式
if (this.debugMode) {
textPiece.style.border = '2px solid red';
textPiece.style.backgroundColor = 'rgba(255, 0, 0, 0.2)';
}
// 根据坐标信息设置位置和大小(coordinate是相对图片的比例坐标)
const coord = piece.coordinate;
const left = offsetX + (coord.x * imageWidth);
const top = offsetY + (coord.y * imageHeight);
const width = coord.width * imageWidth;
const height = coord.height * imageHeight;
textPiece.style.left = `${left}px`;
textPiece.style.top = `${top}px`;
textPiece.style.width = `${width}px`;
textPiece.style.height = `${height}px`;
if (this.debugMode) {
console.log(`Piece ${index + 1} (${piece.original}):`, {
coord,
left,
top,
width,
height
});
}
// 创建文本内容
const originalText = document.createElement('div');
originalText.className = 'piece-text';
originalText.textContent = piece.original;
const translationText = document.createElement('div');
translationText.className = 'piece-translation';
translationText.textContent = piece.translation;
textPiece.appendChild(originalText);
textPiece.appendChild(translationText);
// 点击事件
textPiece.addEventListener('click', () => this.playPiece(piece, textPiece));
textOverlays.appendChild(textPiece);
});
// 根据设置显示翻译
if (this.showTranslation || this.settings.autoTranslation) {
textOverlays.classList.add('show-translation');
}
};
// 如果图片已经加载完成,直接渲染
if (pageImage.complete && pageImage.naturalHeight !== 0) {
renderPieces();
} else {
// 否则等待图片加载完成
pageImage.addEventListener('load', renderPieces, { once: true });
}
// 监听窗口大小变化和图片加载事件
let resizeTimeout;
const handleResize = () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
if (pageImage.complete && pageImage.naturalHeight !== 0) {
console.log('窗口大小变化,重新计算坐标');
renderPieces();
}
}, 100);
};
// 移除之前的监听器(如果存在)
if (pageImage.resizeObserver) {
pageImage.resizeObserver.disconnect();
}
// 创建新的监听器
pageImage.resizeObserver = new ResizeObserver(handleResize);
pageImage.resizeObserver.observe(pageImage);
// 同时监听窗口大小变化
window.addEventListener('resize', handleResize);
}
async playPiece(piece, element) {
try {
// 移除其他片段的激活状态
document.querySelectorAll('.text-piece').forEach(el => {
el.classList.remove('active', 'playing');
});
// 激活当前片段
element.classList.add('active');
// 显示音频播放器
const audioPlayer = document.getElementById('audioPlayer');
audioPlayer.style.display = 'block';
// 更新音频文本显示
const audioText = document.getElementById('audioText');
audioText.innerHTML = `
${piece.original}
${piece.translation}
`;
// 加载并播放音频
const audio = document.getElementById('audio');
const audioUrl = piece.originSoundUrl || piece.encryptSoundUrl;
if (!audioUrl) {
this.showToast('该片段没有可用的音频', 'warning');
return;
}
// 停止当前播放的音频
if (this.currentAudio && !this.currentAudio.paused) {
this.currentAudio.pause();
}
audio.src = audioUrl;
audio.playbackRate = this.settings.playbackSpeed;
this.currentAudio = audio;
// 播放音频
await audio.play();
this.isPlaying = true;
element.classList.add('playing');
this.updatePlayButton();
} catch (error) {
console.error('音频播放失败:', error);
this.showToast('音频播放失败', 'error');
element.classList.remove('active', 'playing');
}
}
async playAllPieces() {
const page = this.bookData[this.currentPageIndex];
if (!page || !page.pieces.length) return;
let currentPieceIndex = 0;
const playNext = async () => {
if (currentPieceIndex >= page.pieces.length) {
this.showToast('整页播放完成', 'success');
return;
}
const piece = page.pieces[currentPieceIndex];
const element = document.querySelector(`[data-piece-index="${currentPieceIndex}"]`);
if (element) {
await this.playPiece(piece, element);
// 等待当前音频播放完成
const audio = document.getElementById('audio');
audio.addEventListener('ended', () => {
currentPieceIndex++;
setTimeout(playNext, 500); // 稍微延迟播放下一个
}, { once: true });
} else {
currentPieceIndex++;
playNext();
}
};
playNext();
}
togglePlayPause() {
const audio = document.getElementById('audio');
if (!audio.src) return;
if (this.isPlaying) {
audio.pause();
this.isPlaying = false;
} else {
audio.play().then(() => {
this.isPlaying = true;
}).catch(error => {
console.error('播放失败:', error);
this.showToast('播放失败', 'error');
});
}
this.updatePlayButton();
}
repeatAudio() {
const audio = document.getElementById('audio');
if (audio.src) {
audio.currentTime = 0;
if (!this.isPlaying) {
this.togglePlayPause();
}
}
}
seekAudio(event) {
const audio = document.getElementById('audio');
if (!audio.src || !audio.duration) return;
const timeline = document.getElementById('audioTimeline');
const rect = timeline.getBoundingClientRect();
const percentage = (event.clientX - rect.left) / rect.width;
const newTime = percentage * audio.duration;
audio.currentTime = newTime;
}
updatePlayButton() {
const playPauseBtn = document.getElementById('playPauseBtn');
const icon = playPauseBtn.querySelector('i');
if (this.isPlaying) {
icon.className = 'fas fa-pause';
} else {
icon.className = 'fas fa-play';
}
}
updateAudioUI() {
const audio = document.getElementById('audio');
const totalTime = document.getElementById('totalTime');
if (audio.duration) {
totalTime.textContent = this.formatTime(audio.duration);
}
}
updateAudioProgress() {
const audio = document.getElementById('audio');
const progressBar = document.getElementById('audioProgressBar');
const currentTime = document.getElementById('currentTime');
if (audio.duration) {
const percentage = (audio.currentTime / audio.duration) * 100;
progressBar.style.width = `${percentage}%`;
currentTime.textContent = this.formatTime(audio.currentTime);
}
}
onAudioEnded() {
this.isPlaying = false;
this.updatePlayButton();
// 移除播放状态
document.querySelectorAll('.text-piece').forEach(el => {
el.classList.remove('playing');
});
// 如果启用自动播放下一个
if (this.settings.autoPlayNext) {
// 这里可以实现自动播放下一个片段的逻辑
}
}
toggleTranslation() {
this.showTranslation = !this.showTranslation;
const textOverlays = document.getElementById('textOverlays');
const translationBtn = document.getElementById('translationToggle');
if (this.showTranslation) {
textOverlays.classList.add('show-translation');
translationBtn.classList.add('active');
} else {
textOverlays.classList.remove('show-translation');
translationBtn.classList.remove('active');
}
}
toggleInteractiveAreas() {
this.showInteractiveAreas = !this.showInteractiveAreas;
const textOverlays = document.getElementById('textOverlays');
const interactiveBtn = document.getElementById('interactiveToggle');
if (this.showInteractiveAreas) {
textOverlays.classList.add('show-interactive-areas');
interactiveBtn.classList.add('active');
interactiveBtn.querySelector('i').className = 'fas fa-eye-slash';
interactiveBtn.title = '隐藏交互区域';
} else {
textOverlays.classList.remove('show-interactive-areas');
interactiveBtn.classList.remove('active');
interactiveBtn.querySelector('i').className = 'fas fa-eye';
interactiveBtn.title = '显示交互区域';
}
}
previousPage() {
if (this.currentPageIndex > 0) {
this.currentPageIndex--;
this.renderCurrentPage();
this.updateUI();
this.stopCurrentAudio();
}
}
nextPage() {
if (this.currentPageIndex < this.bookData.length - 1) {
this.currentPageIndex++;
this.renderCurrentPage();
this.updateUI();
this.stopCurrentAudio();
}
}
stopCurrentAudio() {
if (this.currentAudio && !this.currentAudio.paused) {
this.currentAudio.pause();
this.isPlaying = false;
this.updatePlayButton();
}
// 隐藏音频播放器
document.getElementById('audioPlayer').style.display = 'none';
// 移除所有激活状态
document.querySelectorAll('.text-piece').forEach(el => {
el.classList.remove('active', 'playing');
});
}
updateUI() {
// 更新页面信息
document.getElementById('currentPage').textContent = this.currentPageIndex + 1;
document.getElementById('totalPages').textContent = this.bookData.length;
// 更新进度条
const progress = ((this.currentPageIndex + 1) / this.bookData.length) * 100;
document.getElementById('progressFill').style.width = `${progress}%`;
// 更新导航按钮状态
document.getElementById('prevBtn').disabled = this.currentPageIndex === 0;
document.getElementById('nextBtn').disabled = this.currentPageIndex === this.bookData.length - 1;
// 更新书签按钮状态
this.updateBookmarkButton();
}
showSettings() {
document.getElementById('settingsPanel').style.display = 'flex';
// 同步当前设置到UI
document.getElementById('autoTranslation').checked = this.settings.autoTranslation;
document.getElementById('playbackSpeed').value = this.settings.playbackSpeed;
document.getElementById('autoPlayNext').checked = this.settings.autoPlayNext;
}
hideSettings() {
document.getElementById('settingsPanel').style.display = 'none';
}
loadSettings() {
const saved = localStorage.getItem('learningAppSettings');
if (saved) {
try {
this.settings = { ...this.settings, ...JSON.parse(saved) };
} catch (error) {
console.error('设置加载失败:', error);
}
}
}
saveSettings() {
localStorage.setItem('learningAppSettings', JSON.stringify(this.settings));
}
handleKeyPress(event) {
// 如果设置面板打开,只处理 Escape 键
if (document.getElementById('settingsPanel').style.display === 'flex') {
if (event.key === 'Escape') {
this.hideSettings();
}
return;
}
switch (event.key) {
case 'ArrowLeft':
event.preventDefault();
this.previousPage();
break;
case 'ArrowRight':
event.preventDefault();
this.nextPage();
break;
case ' ':
event.preventDefault();
this.togglePlayPause();
break;
case 't':
case 'T':
event.preventDefault();
this.toggleTranslation();
break;
case 'i':
case 'I':
event.preventDefault();
this.toggleInteractiveAreas();
break;
case 'r':
case 'R':
event.preventDefault();
this.repeatAudio();
break;
case 'p':
case 'P':
event.preventDefault();
this.playAllPieces();
break;
case 'b':
case 'B':
event.preventDefault();
this.toggleBookmark();
break;
case 'f':
case 'F':
event.preventDefault();
this.showSearch();
break;
case 'd':
case 'D':
if (event.ctrlKey) {
event.preventDefault();
this.toggleDebugMode();
}
break;
}
}
showToast(message, type = 'info') {
const toast = document.getElementById('toast');
toast.textContent = message;
toast.className = `toast ${type} show`;
setTimeout(() => {
toast.classList.remove('show');
}, 3000);
}
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
// 搜索功能
showSearch() {
document.getElementById('searchPanel').style.display = 'flex';
document.getElementById('searchInput').focus();
}
hideSearch() {
document.getElementById('searchPanel').style.display = 'none';
document.getElementById('searchInput').value = '';
this.clearSearchResults();
}
performSearch() {
const query = document.getElementById('searchInput').value.trim();
if (!query) {
this.showToast('请输入搜索关键词', 'warning');
return;
}
const results = [];
const regex = new RegExp(query, 'gi');
this.bookData.forEach((page, pageIndex) => {
page.pieces.forEach((piece, pieceIndex) => {
if (regex.test(piece.original) || regex.test(piece.translation)) {
results.push({
pageIndex,
pieceIndex,
page: page.pageNumber,
original: piece.original,
translation: piece.translation,
piece
});
}
});
});
this.searchResults = results;
this.displaySearchResults(query);
}
displaySearchResults(query) {
const resultsContainer = document.getElementById('searchResults');
if (this.searchResults.length === 0) {
resultsContainer.innerHTML = '未找到匹配的内容
';
return;
}
const regex = new RegExp(`(${query})`, 'gi');
let html = '';
this.searchResults.forEach((result, index) => {
const highlightedOriginal = result.original.replace(regex, '$1');
const highlightedTranslation = result.translation.replace(regex, '$1');
html += `
第 ${result.page} 页
${highlightedOriginal}
${highlightedTranslation}
`;
});
resultsContainer.innerHTML = html;
// 添加点击事件
resultsContainer.querySelectorAll('.search-result-item').forEach(item => {
item.addEventListener('click', (e) => {
const index = parseInt(e.currentTarget.dataset.resultIndex);
this.goToSearchResult(index);
});
});
}
goToSearchResult(index) {
const result = this.searchResults[index];
if (!result) return;
this.currentPageIndex = result.pageIndex;
this.renderCurrentPage();
this.updateUI();
this.hideSearch();
// 稍微延迟后高亮显示并播放对应片段
setTimeout(() => {
const element = document.querySelector(`[data-piece-index="${result.pieceIndex}"]`);
if (element) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
this.playPiece(result.piece, element);
}
}, 500);
this.showToast(`跳转到第 ${result.page} 页`, 'success');
}
clearSearchResults() {
this.searchResults = [];
document.getElementById('searchResults').innerHTML = '输入关键词开始搜索
';
}
// 书签功能
toggleBookmark() {
const isBookmarked = this.bookmarks.some(b => b.pageIndex === this.currentPageIndex);
if (isBookmarked) {
const bookmark = this.bookmarks.find(b => b.pageIndex === this.currentPageIndex);
this.removeBookmark(bookmark.id);
} else {
this.addBookmark();
}
}
showBookmarks() {
document.getElementById('bookmarkPanel').style.display = 'flex';
this.renderBookmarks();
}
hideBookmarks() {
document.getElementById('bookmarkPanel').style.display = 'none';
}
addBookmark() {
const currentPage = this.bookData[this.currentPageIndex];
if (!currentPage) return;
const bookmark = {
id: Date.now(),
pageIndex: this.currentPageIndex,
pageNumber: currentPage.pageNumber,
title: `第 ${currentPage.pageNumber} 页`,
timestamp: new Date().toISOString()
};
// 检查是否已存在
const exists = this.bookmarks.some(b => b.pageIndex === this.currentPageIndex);
if (exists) {
this.showToast('当前页面已在书签中', 'warning');
return;
}
this.bookmarks.push(bookmark);
this.saveBookmarks();
this.updateBookmarkButton();
this.showToast('书签添加成功', 'success');
}
removeBookmark(bookmarkId) {
this.bookmarks = this.bookmarks.filter(b => b.id !== bookmarkId);
this.saveBookmarks();
this.updateBookmarkButton();
this.renderBookmarks();
this.showToast('书签删除成功', 'success');
}
renderBookmarks() {
const container = document.getElementById('bookmarkList');
if (this.bookmarks.length === 0) {
container.innerHTML = '还没有添加任何书签
';
return;
}
let html = '';
this.bookmarks.forEach(bookmark => {
const date = new Date(bookmark.timestamp);
const timeString = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
html += `
${bookmark.title}
${timeString}
`;
});
container.innerHTML = html;
// 添加点击事件
container.querySelectorAll('.bookmark-item').forEach(item => {
item.addEventListener('click', (e) => {
if (e.target.closest('.bookmark-delete')) return;
const pageIndex = parseInt(item.dataset.pageIndex);
this.goToBookmark(pageIndex);
});
});
container.querySelectorAll('.bookmark-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const bookmarkId = parseInt(btn.dataset.bookmarkId);
this.removeBookmark(bookmarkId);
});
});
}
goToBookmark(pageIndex) {
this.currentPageIndex = pageIndex;
this.renderCurrentPage();
this.updateUI();
this.hideBookmarks();
this.showToast(`跳转到第 ${this.bookData[pageIndex].pageNumber} 页`, 'success');
}
updateBookmarkButton() {
const btn = document.getElementById('bookmarkBtn');
const isBookmarked = this.bookmarks.some(b => b.pageIndex === this.currentPageIndex);
if (isBookmarked) {
btn.classList.add('active');
btn.title = '取消书签';
} else {
btn.classList.remove('active');
btn.title = '添加书签';
}
}
loadBookmarks() {
const saved = localStorage.getItem('learningAppBookmarks');
if (saved) {
try {
this.bookmarks = JSON.parse(saved);
} catch (error) {
console.error('书签加载失败:', error);
this.bookmarks = [];
}
}
}
saveBookmarks() {
localStorage.setItem('learningAppBookmarks', JSON.stringify(this.bookmarks));
}
// 调试模式
toggleDebugMode() {
this.debugMode = !this.debugMode;
this.showToast(`调试模式: ${this.debugMode ? '开启' : '关闭'}`, 'info');
// 重新渲染当前页面以应用调试样式
this.renderCurrentPage();
}
}
// 页面加载完成后初始化应用
document.addEventListener('DOMContentLoaded', () => {
new InteractiveLearningApp();
});
// 处理页面可见性变化,暂停音频播放
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
const audio = document.getElementById('audio');
if (audio && !audio.paused) {
audio.pause();
}
}
});