a
This commit is contained in:
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* Audiobook Player - App JavaScript
|
||||
* Minimalist immersive reading experience
|
||||
*/
|
||||
|
||||
// Theme Management
|
||||
function initTheme() {
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.remove('light-mode');
|
||||
document.body.classList.add('dark-mode');
|
||||
updateThemeIcon(true);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const isDark = document.body.classList.contains('dark-mode');
|
||||
|
||||
if (isDark) {
|
||||
document.body.classList.remove('dark-mode');
|
||||
document.body.classList.add('light-mode');
|
||||
localStorage.setItem('theme', 'light');
|
||||
} else {
|
||||
document.body.classList.remove('light-mode');
|
||||
document.body.classList.add('dark-mode');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
|
||||
updateThemeIcon(!isDark);
|
||||
}
|
||||
|
||||
function updateThemeIcon(isDark) {
|
||||
const toggle = document.querySelector('.theme-toggle');
|
||||
if (toggle) {
|
||||
toggle.textContent = isDark ? '☀️' : '🌙';
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation
|
||||
function goBack() {
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
function closePlayer() {
|
||||
// Override in player page
|
||||
window.history.back();
|
||||
}
|
||||
|
||||
// Save and Restore Reading Position
|
||||
function saveReadingPosition(storyId, scrollPosition) {
|
||||
const positions = JSON.parse(localStorage.getItem('readingPositions') || '{}');
|
||||
positions[storyId] = {
|
||||
position: scrollPosition,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
localStorage.setItem('readingPositions', JSON.stringify(positions));
|
||||
}
|
||||
|
||||
function getReadingPosition(storyId) {
|
||||
const positions = JSON.parse(localStorage.getItem('readingPositions') || '{}');
|
||||
return positions[storyId]?.position || 0;
|
||||
}
|
||||
|
||||
// Smooth Scroll
|
||||
function scrollToPosition(position) {
|
||||
window.scrollTo({
|
||||
top: position,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
|
||||
// Touch Gestures (for mobile)
|
||||
let touchStartX = 0;
|
||||
let touchEndX = 0;
|
||||
|
||||
function initTouchGestures() {
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
touchStartX = e.changedTouches[0].screenX;
|
||||
}, { passive: true });
|
||||
|
||||
document.addEventListener('touchend', (e) => {
|
||||
touchEndX = e.changedTouches[0].screenX;
|
||||
handleSwipe();
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
function handleSwipe() {
|
||||
const swipeThreshold = 100;
|
||||
const diff = touchStartX - touchEndX;
|
||||
|
||||
// Swipe right (go back)
|
||||
if (diff < -swipeThreshold) {
|
||||
// Only if we're not on the home page
|
||||
if (window.location.pathname !== '/') {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keyboard Shortcuts
|
||||
function initKeyboardShortcuts() {
|
||||
document.addEventListener('keydown', (e) => {
|
||||
// Space: Play/Pause (on player page)
|
||||
if (e.code === 'Space' && document.getElementById('playBtn')) {
|
||||
e.preventDefault();
|
||||
togglePlay();
|
||||
}
|
||||
|
||||
// Escape: Go back
|
||||
if (e.code === 'Escape') {
|
||||
if (window.location.pathname !== '/') {
|
||||
goBack();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Audio Player Enhancements
|
||||
function initAudioPlayer() {
|
||||
const audio = document.getElementById('audioPlayer');
|
||||
if (!audio) return;
|
||||
|
||||
// Handle errors
|
||||
audio.addEventListener('error', (e) => {
|
||||
console.error('Audio error:', e);
|
||||
showNotification('播放出错,请重试');
|
||||
});
|
||||
|
||||
// Handle ended
|
||||
audio.addEventListener('ended', () => {
|
||||
isPlaying = false;
|
||||
updatePlayButton();
|
||||
});
|
||||
|
||||
// Handle loading
|
||||
audio.addEventListener('loadstart', () => {
|
||||
showNotification('加载中...', 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Notification System
|
||||
function showNotification(message, duration = 3000) {
|
||||
// Remove existing notification
|
||||
const existing = document.querySelector('.notification');
|
||||
if (existing) {
|
||||
existing.remove();
|
||||
}
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = 'notification';
|
||||
notification.textContent = message;
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--bg-card);
|
||||
color: var(--text-primary);
|
||||
padding: 12px 24px;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 4px 16px var(--shadow);
|
||||
z-index: 9999;
|
||||
font-size: 0.9rem;
|
||||
animation: slideUp 0.3s ease;
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
if (duration > 0) {
|
||||
setTimeout(() => {
|
||||
notification.style.animation = 'slideDown 0.3s ease';
|
||||
setTimeout(() => notification.remove(), 300);
|
||||
}, duration);
|
||||
}
|
||||
|
||||
return notification;
|
||||
}
|
||||
|
||||
// Add animation styles
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(-50%) translateY(20px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// Prefetch next chunk (optimization)
|
||||
function prefetchNextChunk(currentIndex) {
|
||||
// Implementation for preloading next audio chunk
|
||||
// This would be implemented in player.html
|
||||
}
|
||||
|
||||
// Performance: Debounce scroll events
|
||||
function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initTheme();
|
||||
initTouchGestures();
|
||||
initKeyboardShortcuts();
|
||||
initAudioPlayer();
|
||||
});
|
||||
|
||||
// Handle page visibility change (pause when switching tabs)
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
const audio = document.getElementById('audioPlayer');
|
||||
if (document.hidden && audio && !audio.paused) {
|
||||
// Optional: pause when switching tabs
|
||||
// audio.pause();
|
||||
// isPlaying = false;
|
||||
// updatePlayButton();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent zoom on double tap (mobile)
|
||||
let lastTouchEnd = 0;
|
||||
document.addEventListener('touchend', (event) => {
|
||||
const now = Date.now();
|
||||
if (now - lastTouchEnd <= 300) {
|
||||
event.preventDefault();
|
||||
}
|
||||
lastTouchEnd = now;
|
||||
}, { passive: false });
|
||||
Reference in New Issue
Block a user