/** * 交互式英语学习应用 - 主要逻辑 * 支持点读、翻译显示和音频播放功能 */ 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(); } } });