mirror of
https://github.com/levywang/avhub.git
synced 2026-02-21 08:47:22 +08:00
feat: Add 3D animation effect to cover image
This commit is contained in:
parent
79bdecdc89
commit
76a6259cac
519
web/script.js
519
web/script.js
@ -3,6 +3,12 @@
|
||||
// 添加全局变量
|
||||
let currentTab = 'search'; // 默认标签页
|
||||
|
||||
// 添加视频URL状态管理
|
||||
let currentVideoUrl = ''; // 存储当前视频URL
|
||||
|
||||
// 添加全局变量来跟踪视频播放状态
|
||||
let wasPlaying = false; // 记录切换标签页前的播放状态
|
||||
|
||||
function switchTab(tabName) {
|
||||
// 更新当前标签页
|
||||
currentTab = tabName;
|
||||
@ -10,6 +16,7 @@ function switchTab(tabName) {
|
||||
// 获取所有标签页内容和按钮
|
||||
const tabs = document.querySelectorAll('.tab-content');
|
||||
const buttons = document.querySelectorAll('.tab-button');
|
||||
const videoPlayer = document.getElementById('videoPlayer');
|
||||
|
||||
// 隐藏所有标签页内容
|
||||
tabs.forEach(tab => {
|
||||
@ -27,9 +34,25 @@ function switchTab(tabName) {
|
||||
// 激活对应的按钮
|
||||
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
||||
|
||||
// 如果切换到播放器标签页,加载视频
|
||||
// 处理视频播放状态
|
||||
if (tabName === 'player') {
|
||||
loadVideo();
|
||||
// 切换到播放器标签页
|
||||
if (!currentVideoUrl) {
|
||||
loadVideo();
|
||||
} else if (wasPlaying && videoPlayer) {
|
||||
// 如果之前是播放状态,恢复播放
|
||||
videoPlayer.play().catch(e => console.error('Resume play failed:', e));
|
||||
}
|
||||
} else {
|
||||
// 切换到其他标签页
|
||||
if (videoPlayer) {
|
||||
// 保存当前播放状态
|
||||
wasPlaying = !videoPlayer.paused;
|
||||
// 暂停视频
|
||||
if (!videoPlayer.paused) {
|
||||
videoPlayer.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +216,7 @@ function showCoverImage(searchTerm) {
|
||||
if (!coverImageContainer) {
|
||||
coverImageContainer = document.createElement('div');
|
||||
coverImageContainer.id = 'coverImageContainer';
|
||||
coverImageContainer.className = 'cover-image-container hidden';
|
||||
coverImageContainer.className = 'cover-image-container hidden card-3d';
|
||||
|
||||
// 创建图片元素
|
||||
image = document.createElement('img');
|
||||
@ -206,6 +229,11 @@ function showCoverImage(searchTerm) {
|
||||
|
||||
// 将容器添加到搜索结果之前
|
||||
document.getElementById('searchResults').insertAdjacentElement('beforebegin', coverImageContainer);
|
||||
} else {
|
||||
// 确保容器有3D卡片类
|
||||
if (!coverImageContainer.classList.contains('card-3d')) {
|
||||
coverImageContainer.classList.add('card-3d');
|
||||
}
|
||||
}
|
||||
|
||||
const modal = document.getElementById('imageModal');
|
||||
@ -245,6 +273,9 @@ function showCoverImage(searchTerm) {
|
||||
coverImageContainer.style.transition = 'opacity 0.3s ease';
|
||||
coverImageContainer.style.opacity = '1';
|
||||
image.classList.add('loaded');
|
||||
|
||||
// 添加3D效果初始化
|
||||
initCard3DEffect(coverImageContainer);
|
||||
});
|
||||
};
|
||||
|
||||
@ -254,12 +285,80 @@ function showCoverImage(searchTerm) {
|
||||
};
|
||||
|
||||
// 点击图片显示大图
|
||||
coverImageContainer.onclick = () => {
|
||||
modalImage.src = imageUrl;
|
||||
coverImageContainer.onclick = (e) => {
|
||||
// 获取点击位置相对于容器的坐标
|
||||
const rect = coverImageContainer.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
// 将容器分为3x3的网格,根据点击位置选择不同的图片
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
// 水平分为左、中、右三部分
|
||||
const xSection = Math.floor(x / (width / 3));
|
||||
// 垂直分为上、中、下三部分
|
||||
const ySection = Math.floor(y / (height / 3));
|
||||
|
||||
// 根据9宫格位置选择不同的图片
|
||||
const position = ySection * 3 + xSection;
|
||||
|
||||
// 预加载图片,确保图片存在再显示
|
||||
const preloadImage = new Image();
|
||||
|
||||
// 所有位置都使用相同的图片URL
|
||||
const newImageUrl = imageUrl;
|
||||
|
||||
// 显示加载指示器
|
||||
const loadingIndicator = document.createElement('div');
|
||||
loadingIndicator.className = 'modal-loading';
|
||||
modal.querySelector('.modal-content').appendChild(loadingIndicator);
|
||||
|
||||
// 预加载图片
|
||||
preloadImage.onload = () => {
|
||||
// 图片加载成功,设置src并显示模态框
|
||||
modalImage.src = newImageUrl;
|
||||
modalImage.classList.add('fullwidth-preview');
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
// 移除加载指示器
|
||||
if (loadingIndicator.parentNode) {
|
||||
loadingIndicator.parentNode.removeChild(loadingIndicator);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
modal.classList.add('active');
|
||||
}, 10);
|
||||
};
|
||||
|
||||
preloadImage.onerror = () => {
|
||||
console.log(`预览图 ${newImageUrl} 加载失败`);
|
||||
|
||||
// 移除加载指示器
|
||||
if (loadingIndicator.parentNode) {
|
||||
loadingIndicator.parentNode.removeChild(loadingIndicator);
|
||||
}
|
||||
|
||||
// 显示错误提示
|
||||
modalImage.classList.add('error');
|
||||
modal.classList.remove('hidden');
|
||||
|
||||
setTimeout(() => {
|
||||
modal.classList.add('active');
|
||||
}, 10);
|
||||
};
|
||||
|
||||
// 开始加载图片
|
||||
preloadImage.src = newImageUrl;
|
||||
|
||||
// 先显示模态框,但图片为空
|
||||
modalImage.src = '';
|
||||
modalImage.classList.remove('error');
|
||||
modalImage.classList.remove('fullwidth-preview');
|
||||
modal.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
modal.classList.add('active');
|
||||
}, 10);
|
||||
|
||||
// 显示模态框时初始化事件
|
||||
initializeModalEvents();
|
||||
};
|
||||
} else {
|
||||
// 如果不是番号格式,隐藏图片容器
|
||||
@ -267,39 +366,114 @@ function showCoverImage(searchTerm) {
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化3D卡片效果
|
||||
function initCard3DEffect(card) {
|
||||
if (!card) return;
|
||||
|
||||
// 移除之前可能添加的事件监听器
|
||||
card.removeEventListener('mousemove', handleMouseMove);
|
||||
card.removeEventListener('mouseleave', handleMouseLeave);
|
||||
card.removeEventListener('mouseenter', handleMouseEnter);
|
||||
|
||||
// 添加事件监听器
|
||||
card.addEventListener('mousemove', handleMouseMove);
|
||||
card.addEventListener('mouseleave', handleMouseLeave);
|
||||
card.addEventListener('mouseenter', handleMouseEnter);
|
||||
}
|
||||
|
||||
// 处理鼠标移动事件
|
||||
function handleMouseMove(e) {
|
||||
const card = this;
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
// 计算鼠标位置相对于卡片中心的偏移
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
const deltaX = (x - centerX) / centerX;
|
||||
const deltaY = (y - centerY) / centerY;
|
||||
|
||||
// 减小旋转角度,从15度减小到8度
|
||||
const rotateX = deltaY * -8;
|
||||
const rotateY = deltaX * 8;
|
||||
|
||||
// 应用3D变换,减小缩放比例
|
||||
card.style.transform = `perspective(1500px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(1.02, 1.02, 1.02)`;
|
||||
|
||||
// 减小光影效果的强度
|
||||
card.style.boxShadow = `
|
||||
0 5px 15px rgba(0,0,0,0.2),
|
||||
${deltaX * 5}px ${deltaY * 5}px 15px rgba(0,0,0,0.1)
|
||||
`;
|
||||
|
||||
// 减小图片内部的视差效果
|
||||
const image = card.querySelector('img');
|
||||
if (image) {
|
||||
image.style.transform = `translateX(${deltaX * -5}px) translateY(${deltaY * -5}px)`;
|
||||
|
||||
// 添加亮度调整,使图片在不同角度下亮度均匀
|
||||
const brightness = 1 + (deltaX * 0.05);
|
||||
image.style.filter = `brightness(${brightness})`;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理鼠标离开事件
|
||||
function handleMouseLeave() {
|
||||
const card = this;
|
||||
|
||||
// 重置变换
|
||||
card.style.transform = 'perspective(1500px) rotateX(0) rotateY(0) scale3d(1, 1, 1)';
|
||||
card.style.boxShadow = '0 4px 10px rgba(0, 0, 0, 0.2)';
|
||||
|
||||
// 重置图片位置和滤镜
|
||||
const image = card.querySelector('img');
|
||||
if (image) {
|
||||
image.style.transform = 'translateX(0) translateY(0)';
|
||||
image.style.filter = 'brightness(1)';
|
||||
}
|
||||
}
|
||||
|
||||
// 处理鼠标进入事件
|
||||
function handleMouseEnter() {
|
||||
const card = this;
|
||||
|
||||
// 添加初始变换效果,减小缩放比例
|
||||
card.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
|
||||
card.style.transform = 'perspective(1500px) scale3d(1.01, 1.01, 1.01)';
|
||||
card.style.boxShadow = '0 5px 15px rgba(0,0,0,0.2)';
|
||||
|
||||
// 短暂延迟后移除过渡效果,使鼠标移动时的变换更加流畅
|
||||
setTimeout(() => {
|
||||
card.style.transition = 'none';
|
||||
}, 300); // 增加延迟时间,使过渡更平滑
|
||||
}
|
||||
|
||||
// 视频播放功能
|
||||
let hls = null;
|
||||
let autoplayEnabled = false;
|
||||
let autoNextEnabled = false;
|
||||
let autoplayEnabled = localStorage.getItem('autoplay') === 'true'; // 从localStorage读取初始值
|
||||
let autoNextEnabled = localStorage.getItem('autoNext') === 'true'; // 从localStorage读取初始值
|
||||
|
||||
// 初始化自动播放设置
|
||||
function initializeAutoplaySettings() {
|
||||
const autoplayToggle = document.getElementById('autoplayToggle');
|
||||
if (autoplayToggle) {
|
||||
// 从localStorage读取自动播放设置
|
||||
autoplayEnabled = localStorage.getItem('autoplayEnabled') === 'true';
|
||||
autoplayToggle.checked = autoplayEnabled;
|
||||
|
||||
// 监听自动播放设置变化
|
||||
autoplayToggle.addEventListener('change', (e) => {
|
||||
autoplayEnabled = e.target.checked;
|
||||
localStorage.setItem('autoplayEnabled', autoplayEnabled);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function initializeAutoNextToggle() {
|
||||
const autoNextToggle = document.getElementById('autoNextToggle');
|
||||
if (autoNextToggle) {
|
||||
// 从localStorage读取状态
|
||||
autoNextEnabled = localStorage.getItem('autoNextEnabled') === 'true';
|
||||
autoNextToggle.checked = autoNextEnabled;
|
||||
// 绑定change事件
|
||||
autoNextToggle.addEventListener('change', (e) => {
|
||||
autoNextEnabled = e.target.checked;
|
||||
localStorage.setItem('autoNextEnabled', autoNextEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
// 从localStorage读取设置
|
||||
autoplayToggle.checked = autoplayEnabled;
|
||||
autoNextToggle.checked = autoNextEnabled;
|
||||
|
||||
// 监听自动播放开关变化
|
||||
autoplayToggle.addEventListener('change', function() {
|
||||
autoplayEnabled = this.checked;
|
||||
localStorage.setItem('autoplay', this.checked);
|
||||
});
|
||||
|
||||
// 监听自动下一个开关变化
|
||||
autoNextToggle.addEventListener('change', function() {
|
||||
autoNextEnabled = this.checked;
|
||||
localStorage.setItem('autoNext', this.checked);
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化封面图开关设置
|
||||
@ -334,130 +508,136 @@ function initializeCoverToggle() {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadVideo() {
|
||||
// 修改 loadVideo 函数
|
||||
function loadVideo() {
|
||||
const videoPlayer = document.getElementById('videoPlayer');
|
||||
const videoSourceUrl = document.getElementById('videoSourceUrl');
|
||||
const notification = document.getElementById('notification');
|
||||
const sourceUrlElement = document.getElementById('videoSourceUrl');
|
||||
const showCover = document.getElementById('coverToggle').checked;
|
||||
|
||||
try {
|
||||
// 添加加载中状态
|
||||
videoPlayer.classList.add('loading');
|
||||
|
||||
// 显示加载中通知
|
||||
notification.innerHTML = `
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${translations[currentLang].loadingVideo}</span>
|
||||
`;
|
||||
notification.classList.add('show');
|
||||
|
||||
// 预加载封面图
|
||||
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.VIDEO}`);
|
||||
const data = await response.json();
|
||||
|
||||
// 创建一个Image对象来预加载封面图
|
||||
const img = new Image();
|
||||
img.src = data.img_url;
|
||||
|
||||
// 等待封面图加载完成
|
||||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
img.onerror = resolve; // 如果加载失败也继续
|
||||
});
|
||||
|
||||
// 销毁之前的HLS实例
|
||||
if (hls) {
|
||||
hls.destroy();
|
||||
hls = null;
|
||||
|
||||
// 如果已经有视频URL,则不重新请求
|
||||
if (currentVideoUrl) {
|
||||
if (videoSourceUrl) {
|
||||
videoSourceUrl.textContent = currentVideoUrl;
|
||||
}
|
||||
|
||||
// 设置视频封面(如果开启了封面图显示)
|
||||
if (showCover && data.img_url) {
|
||||
videoPlayer.poster = data.img_url;
|
||||
} else {
|
||||
videoPlayer.poster = ''; // 清除封面图
|
||||
}
|
||||
|
||||
// 更新视频源地址显示
|
||||
sourceUrlElement.textContent = data.url;
|
||||
|
||||
// 根据视频URL类型选择播放方式
|
||||
if (data.url.endsWith('.m3u8')) {
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls();
|
||||
hls.loadSource(data.url);
|
||||
hls.attachMedia(videoPlayer);
|
||||
hls.on(Hls.Events.MANIFEST_PARSED, () => {
|
||||
if (autoplayEnabled) {
|
||||
videoPlayer.play().catch(e => console.error('Auto-play failed:', e));
|
||||
}
|
||||
});
|
||||
} else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
videoPlayer.src = data.url;
|
||||
if (autoplayEnabled) {
|
||||
videoPlayer.play().catch(e => console.error('Auto-play failed:', e));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
videoPlayer.src = data.url;
|
||||
if (videoPlayer && videoPlayer.src !== currentVideoUrl) {
|
||||
videoPlayer.src = currentVideoUrl;
|
||||
// 根据自动播放设置决定是否播放
|
||||
if (autoplayEnabled) {
|
||||
videoPlayer.play().catch(e => console.error('Auto-play failed:', e));
|
||||
}
|
||||
}
|
||||
|
||||
// 确保播放器控件可见
|
||||
videoPlayer.addEventListener('webkitfullscreenchange', () => {
|
||||
if (document.webkitFullscreenElement) {
|
||||
videoPlayer.style.display = 'block';
|
||||
videoPlayer.controls = true;
|
||||
}
|
||||
});
|
||||
|
||||
videoPlayer.addEventListener('fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
videoPlayer.style.display = 'block';
|
||||
videoPlayer.controls = true;
|
||||
}
|
||||
});
|
||||
videoPlayer.addEventListener('ended', ()=> {
|
||||
if (autoNextEnabled) {
|
||||
loadVideo()}
|
||||
;
|
||||
});
|
||||
|
||||
videoPlayer.classList.remove('loading');
|
||||
notification.classList.remove('show');
|
||||
} catch (error) {
|
||||
console.error('加载视频出错:', error);
|
||||
videoPlayer.classList.remove('loading');
|
||||
sourceUrlElement.textContent = '';
|
||||
|
||||
notification.innerHTML = `
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${translations[currentLang].videoError}</span>
|
||||
`;
|
||||
notification.style.background = '#dc2626';
|
||||
notification.classList.add('show');
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
notification.style.background = '';
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载中通知
|
||||
notification.innerHTML = `
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${translations[currentLang].loadingVideo}</span>
|
||||
`;
|
||||
notification.classList.add('show');
|
||||
|
||||
// 如果没有视频URL,则请求新的
|
||||
fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.VIDEO}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data && data.url) {
|
||||
currentVideoUrl = data.url; // 保存视频URL
|
||||
if (videoSourceUrl) {
|
||||
videoSourceUrl.textContent = data.url;
|
||||
}
|
||||
if (videoPlayer) {
|
||||
// 设置视频封面(如果开启了封面图显示)
|
||||
if (showCover && data.img_url) {
|
||||
videoPlayer.poster = data.img_url;
|
||||
} else {
|
||||
videoPlayer.poster = ''; // 清除封面图
|
||||
}
|
||||
|
||||
videoPlayer.src = data.url;
|
||||
|
||||
// 根据自动播放设置决定是否播放
|
||||
if (autoplayEnabled) {
|
||||
videoPlayer.play().catch(e => console.error('Auto-play failed:', e));
|
||||
}
|
||||
|
||||
// 添加视频结束事件监听
|
||||
videoPlayer.onended = () => {
|
||||
if (autoNextEnabled) {
|
||||
clearVideoUrl(); // 清除当前URL
|
||||
loadVideo(); // 加载下一个视频
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 隐藏加载通知
|
||||
notification.classList.remove('show');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('加载视频失败:', error);
|
||||
notification.innerHTML = `
|
||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>${translations[currentLang].videoError}</span>
|
||||
`;
|
||||
notification.style.background = '#dc2626';
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('show');
|
||||
notification.style.background = '';
|
||||
}, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化视频播放器
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeAutoplaySettings();
|
||||
initializeAutoplaySettings(); // 这个函数现在已经包含了自动播放和自动下一个的初始化
|
||||
initializeCopyButton();
|
||||
initializeCoverToggle();
|
||||
initializeAutoNextToggle(); // 新增初始化
|
||||
|
||||
// 修改下一个按钮的事件监听
|
||||
const nextVideoButton = document.getElementById('nextVideo');
|
||||
if (nextVideoButton) {
|
||||
nextVideoButton.addEventListener('click', loadVideo);
|
||||
nextVideoButton.addEventListener('click', () => {
|
||||
clearVideoUrl(); // 使用clearVideoUrl函数来处理
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化模态框关闭功能
|
||||
const imageModal = document.getElementById('imageModal');
|
||||
const closeModal = document.getElementById('closeModal');
|
||||
|
||||
if (imageModal && closeModal) {
|
||||
// 点击关闭按钮关闭模态框
|
||||
closeModal.addEventListener('click', () => {
|
||||
imageModal.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
imageModal.classList.add('hidden');
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// 点击模态框背景关闭模态框
|
||||
imageModal.addEventListener('click', (e) => {
|
||||
if (e.target === imageModal) {
|
||||
imageModal.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
imageModal.classList.add('hidden');
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
|
||||
// ESC键关闭模态框
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !imageModal.classList.contains('hidden')) {
|
||||
imageModal.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
imageModal.classList.add('hidden');
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -802,7 +982,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 下一个视频按钮
|
||||
const nextVideoButton = document.getElementById('nextVideo');
|
||||
if (nextVideoButton) {
|
||||
nextVideoButton.addEventListener('click', loadVideo);
|
||||
nextVideoButton.addEventListener('click', () => {
|
||||
clearVideoUrl(); // 使用clearVideoUrl函数来处理
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -1067,6 +1249,14 @@ function setLanguage(lang) {
|
||||
el.textContent = translations[lang][messageKey];
|
||||
}
|
||||
});
|
||||
|
||||
// 更新模态框关闭提示文本
|
||||
const modal = document.getElementById('imageModal');
|
||||
if (modal) {
|
||||
modal.setAttribute('data-close-text',
|
||||
lang === 'en' ? 'Click anywhere to close' : '点击任意位置关闭'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新分页控件文本
|
||||
@ -1538,15 +1728,52 @@ function showThemeMenu(button) {
|
||||
document.addEventListener('keydown', handleEscape);
|
||||
}
|
||||
|
||||
// 注册 Service Worker
|
||||
if ('serviceWorker' in navigator) {
|
||||
// 修改 Service Worker 注册代码
|
||||
if ('serviceWorker' in navigator && window.location.protocol === 'https:') {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
navigator.serviceWorker.register('./sw.js') // 使用相对路径
|
||||
.then(registration => {
|
||||
console.log('ServiceWorker registration successful');
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('ServiceWorker registration failed: ', err);
|
||||
console.error('ServiceWorker registration failed: ', err);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log('ServiceWorker is not supported or requires HTTPS');
|
||||
}
|
||||
|
||||
// 修改模态框点击事件处理
|
||||
function initializeModalEvents() {
|
||||
const modal = document.getElementById('imageModal');
|
||||
const modalImage = document.getElementById('modalImage');
|
||||
|
||||
// 设置关闭提示文本
|
||||
modal.setAttribute('data-close-text',
|
||||
currentLang === 'en' ? 'Click anywhere to close' : '点击任意位置关闭'
|
||||
);
|
||||
|
||||
// 点击模态框任意位置关闭
|
||||
modal.addEventListener('click', (e) => {
|
||||
// 添加关闭动画
|
||||
modal.classList.remove('active');
|
||||
modalImage.style.transform = 'scale(0.9)';
|
||||
modalImage.style.opacity = '0';
|
||||
|
||||
// 延迟隐藏模态框,等待动画完成
|
||||
setTimeout(() => {
|
||||
modal.classList.add('hidden');
|
||||
// 重置图片样式
|
||||
modalImage.style.transform = '';
|
||||
modalImage.style.opacity = '';
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
// 添加清除视频URL的函数(可以在需要重新加载视频时调用)
|
||||
function clearVideoUrl() {
|
||||
if (currentVideoUrl) { // 只有在有当前URL时才清除并重新加载
|
||||
currentVideoUrl = '';
|
||||
loadVideo();
|
||||
}
|
||||
}
|
||||
|
||||
424
web/style.css
424
web/style.css
@ -835,53 +835,66 @@ body.light-theme select option {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 32px;
|
||||
width: 100%;
|
||||
position: static;
|
||||
background: var(--background-dark);
|
||||
border-radius: 8px;
|
||||
margin: 20px auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* 加载动画 */
|
||||
/* 加载动画圆圈 */
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid var(--border-color);
|
||||
border-top-color: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
transform-origin: center center;
|
||||
animation: loading-spin 1s linear infinite;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 加载动画旋转效果 */
|
||||
@keyframes loading-spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 加载文本样式 */
|
||||
.loading-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 加载文本点动画 */
|
||||
.loading-text::after {
|
||||
content: '';
|
||||
animation: dots 1.5s steps(4, end) infinite;
|
||||
animation: loading-dots 1.5s steps(4, end) infinite;
|
||||
}
|
||||
|
||||
@keyframes dots {
|
||||
/* 点动画关键帧 */
|
||||
@keyframes loading-dots {
|
||||
0%, 20% { content: ''; }
|
||||
40% { content: '.'; }
|
||||
60% { content: '..'; }
|
||||
80%, 100% { content: '...'; }
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
/* 搜索结果容器 */
|
||||
#searchResults {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 亮色主题下的标签页容器样式 */
|
||||
[data-theme="light"] .tab-container {
|
||||
background: #ffffff;
|
||||
border-color: #e5e7eb;
|
||||
/* 搜索结果中的加载容器 */
|
||||
#searchResults .loading-container {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Logo 样式 */
|
||||
@ -995,31 +1008,37 @@ body.light-theme select option {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* 视频源地址样式 */
|
||||
/* 视频源地址容器样式 */
|
||||
.video-source {
|
||||
background: var(--bg-color);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap; /* 防止换行 */
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
}
|
||||
|
||||
.source-url {
|
||||
color: var(--text-color);
|
||||
padding: 0.25rem 0;
|
||||
word-break: break-all;
|
||||
/* 视频源URL容器 */
|
||||
.video-source .flex-grow {
|
||||
min-width: 0; /* 允许容器缩小 */
|
||||
flex-shrink: 1; /* 允许收缩 */
|
||||
}
|
||||
|
||||
/* 复制按钮样式 */
|
||||
.copy-button {
|
||||
background: var(--primary-color);
|
||||
color: var(--bg-color);
|
||||
transition: all 0.2s;
|
||||
flex-shrink: 0; /* 防止按钮缩小 */
|
||||
white-space: nowrap; /* 防止文字换行 */
|
||||
padding: 0.5rem 1rem;
|
||||
min-width: fit-content; /* 确保按钮宽度适应内容 */
|
||||
}
|
||||
|
||||
.copy-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.copy-button.copied {
|
||||
background: var(--success-color);
|
||||
/* 源地址URL样式 */
|
||||
.source-url {
|
||||
text-overflow: ellipsis; /* 超出显示省略号 */
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 深色主题适配 */
|
||||
@ -1039,38 +1058,114 @@ body.light-theme select option {
|
||||
margin: 20px auto;
|
||||
text-align: center;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||||
cursor: pointer;
|
||||
min-height: 300px; /* 添加最小高度 */
|
||||
min-height: 300px;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.cover-image-container::before {
|
||||
/* 添加提示文本,告诉用户可以点击不同区域 */
|
||||
.cover-image-container::after {
|
||||
content: '点击不同区域查看更多图片';
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cover-image-container:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 增强模态框中的图片效果 */
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image-modal.active .modal-image {
|
||||
opacity: 1;
|
||||
animation: zoomIn 0.3s forwards;
|
||||
}
|
||||
|
||||
@keyframes zoomIn {
|
||||
from {
|
||||
transform: scale(0.9);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 修改3D卡片效果样式,减小抖动幅度 */
|
||||
.card-3d {
|
||||
transform-style: preserve-3d;
|
||||
transform: perspective(1500px); /* 增加透视距离,减小变形效果 */
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
will-change: transform, box-shadow;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 修改光影效果,使光线更均匀 */
|
||||
.card-3d::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
rgba(255,255,255,0.1) 25%,
|
||||
rgba(255,255,255,0.2) 50%,
|
||||
rgba(255,255,255,0.1) 75%
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
rgba(255, 255, 255, 0.1) 0%,
|
||||
rgba(255, 255, 255, 0.05) 50%,
|
||||
rgba(255, 255, 255, 0.1) 100%
|
||||
);
|
||||
animation: shimmer 1.5s infinite;
|
||||
z-index: 1;
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.card-3d:hover::before {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* 移除辅助线 */
|
||||
.card-3d:hover::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 修改图片过渡效果,使其更平滑 */
|
||||
.card-3d img {
|
||||
transition: transform 0.3s ease;
|
||||
transform-origin: center center;
|
||||
backface-visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
filter: brightness(1.02); /* 轻微提高亮度 */
|
||||
}
|
||||
|
||||
/* 确保图片加载后显示正常 */
|
||||
.cover-image {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
@ -1089,7 +1184,7 @@ body.light-theme select option {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -1097,6 +1192,7 @@ body.light-theme select option {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.image-modal.active {
|
||||
@ -1110,26 +1206,16 @@ body.light-theme select option {
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
max-width: 90%;
|
||||
max-width: 96vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
right: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
transition: transform 0.2s ease;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
@ -1141,15 +1227,19 @@ body.light-theme select option {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
/* 添加键盘操作提示 */
|
||||
.modal-content::after {
|
||||
content: 'ESC 关闭';
|
||||
position: absolute;
|
||||
bottom: -30px;
|
||||
/* 修改点击关闭提示,支持中英文 */
|
||||
.image-modal::after {
|
||||
content: attr(data-close-text);
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 下一个按钮样式 */
|
||||
@ -1157,8 +1247,11 @@ body.light-theme select option {
|
||||
background-color: var(--primary-color);
|
||||
color: var(--text-color);
|
||||
border-radius: 0.375rem;
|
||||
font-weight: 500;
|
||||
font-weight: 700; /* 加粗文字 */
|
||||
transition: all 0.2s;
|
||||
padding: 0.5rem 1rem;
|
||||
white-space: nowrap; /* 防止文字换行 */
|
||||
flex-shrink: 0; /* 防止缩小 */
|
||||
}
|
||||
|
||||
.next-button:hover {
|
||||
@ -1244,6 +1337,46 @@ body.light-theme select option {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap; /* 防止换行 */
|
||||
gap: 1rem; /* 添加间距 */
|
||||
}
|
||||
|
||||
/* 自动播放开关容器 */
|
||||
.autoplay-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap; /* 防止文字换行 */
|
||||
flex-shrink: 0; /* 防止缩小 */
|
||||
}
|
||||
|
||||
/* 确保开关文字不换行 */
|
||||
.autoplay-toggle label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 调整开关间距 */
|
||||
.video-controls .autoplay-toggle + .autoplay-toggle {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* 确保移动端布局正确 */
|
||||
@media (max-width: 640px) {
|
||||
.video-controls {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.autoplay-toggle label {
|
||||
font-size: 0.875rem; /* 稍微减小字体大小 */
|
||||
}
|
||||
|
||||
.next-button {
|
||||
padding: 0.5rem 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* 自动播放开关样式 */
|
||||
@ -1498,3 +1631,152 @@ main {
|
||||
z-index: 50;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
/* 添加模态框加载指示器 */
|
||||
.modal-loading {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 5px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--primary-color);
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: translate(-50%, -50%) rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 确保模态框内容区域有相对定位 */
|
||||
.modal-content {
|
||||
position: relative;
|
||||
max-width: 90%;
|
||||
max-height: 90vh;
|
||||
min-height: 200px;
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 改进模态框图片加载过程 */
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image-modal.active .modal-image {
|
||||
opacity: 1;
|
||||
animation: zoomIn 0.3s forwards;
|
||||
}
|
||||
|
||||
/* 添加键盘事件处理,支持ESC关闭和方向键切换图片 */
|
||||
.image-modal.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* 添加图片加载失败时的提示 */
|
||||
.modal-image.error::before {
|
||||
content: '图片加载失败';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 全宽预览图样式 */
|
||||
.fullwidth-preview {
|
||||
width: 96vw !important;
|
||||
max-width: 96vw !important;
|
||||
height: auto !important;
|
||||
max-height: 90vh !important;
|
||||
object-fit: contain !important;
|
||||
}
|
||||
|
||||
/* 改进模态框样式以适应全宽图片 */
|
||||
.modal-content {
|
||||
position: relative;
|
||||
max-width: 96vw;
|
||||
max-height: 90vh;
|
||||
min-height: 200px;
|
||||
min-width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 确保模态框内容居中 */
|
||||
.image-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
/* 改进模态框图片加载过程 */
|
||||
.modal-image {
|
||||
max-width: 100%;
|
||||
max-height: 90vh;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.5);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 添加图片加载错误状态 */
|
||||
.modal-image.error {
|
||||
min-width: 300px;
|
||||
min-height: 200px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.modal-image.error::after {
|
||||
content: '图片加载失败';
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* 添加提示文本 */
|
||||
.image-modal::after {
|
||||
content: '点击任意位置关闭';
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 4px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user