加入照片功能

This commit is contained in:
DelLevin-Home
2026-05-18 02:23:58 +08:00
parent 5e33d53ea3
commit f0605dca4c
25 changed files with 746 additions and 40 deletions

View File

@@ -107,6 +107,41 @@
</div>
</div>
<!-- 人生相册按钮 -->
<div class="photo-album-btn-container">
<button id="photo-album-btn" class="photo-album-btn" title="人生相册">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" stroke="#666" stroke-width="2"/>
<circle cx="8.5" cy="8.5" r="1.5" fill="#666"/>
<path d="M21 15L16 10L5 21" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span class="btn-label" data-i18n="btn_photo_album">人生相册</span>
</button>
<!-- Hover 浮动预览面板 -->
<div id="photo-album-preview" class="photo-album-preview">
<div class="preview-header">
<h3 data-i18n="photo_latest_photos">最新照片</h3>
</div>
<div class="preview-body photo-album-list">
<!-- 最新照片预览将在这里动态加载 -->
</div>
</div>
</div>
<!-- 完整人生相册弹窗(点击后显示) -->
<div id="photo-album-full-modal" class="guestbook-full-modal">
<div class="full-modal-content">
<div class="guestbook-header">
<h2 data-i18n="photo_album_title">人生相册</h2>
<button id="photo-album-close" class="guestbook-close" title="关闭">&times;</button>
</div>
<div class="guestbook-body photo-album-body">
<!-- 照片网格将在这里动态加载 -->
</div>
</div>
</div>
<!-- 完整留言板弹窗(点击后显示) -->
<div id="guestbook-full-modal" class="guestbook-full-modal">
<div class="full-modal-content">
@@ -328,6 +363,7 @@
<script src="./static/js/map-footprint.js"></script>
<script src="./static/js/guestbook.js"></script>
<script src="./static/js/blog_posts.js"></script>
<script src="./static/js/photo_album.js"></script>
<script src="./static/js/tabchange.js"></script>
</body>

61
scripts/README.md Normal file
View File

@@ -0,0 +1,61 @@
# 人生相册构建脚本
## 📖 功能说明
自动扫描 `/static/img/photos/` 目录下的所有图片文件,生成 `photos.json` 索引文件。
## 🚀 使用方法
### 方法一:双击运行(推荐)
直接双击 `build-photos.bat` 文件即可。
### 方法二:命令行运行
```bash
# 在项目根目录下执行
python scripts/generate-photos-json.py
```
## 📝 工作流程
1. 将新照片放入 `/static/img/photos/` 目录
2. 运行构建脚本(双击 `build-photos.bat`
3. 脚本会自动:
- 扫描目录下所有图片文件(支持 jpg, png, gif, webp, bmp, svg
- 按文件名排序
- 生成/更新 `photos.json` 文件
4. 刷新网页,新照片自动显示
## 📂 生成的 JSON 格式
```json
{
"photos": [
"1 (1).jpg",
"1 (2).jpg",
"..."
],
"total": 13,
"lastUpdated": "2026-03-03 15:30:00"
}
```
## ⚙️ 配置说明
支持的图片格式:
- `.jpg` / `.jpeg`
- `.png`
- `.gif`
- `.webp`
- `.bmp`
- `.svg`
如需添加其他格式,编辑 `generate-photos-json.py` 中的 `IMAGE_EXTENSIONS` 集合。
## 💡 提示
- 照片会按文件名**字母顺序**排序
- 建议使用数字前缀控制顺序,如:`001.jpg`, `002.jpg`
- 脚本会自动排除 `photos.json` 文件本身
- 每次运行都会重新扫描并覆盖旧的 JSON 文件

14
scripts/build-photos.bat Normal file
View File

@@ -0,0 +1,14 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 人生相册构建脚本
echo ========================================
echo.
cd /d "%~dp0.."
python scripts\generate-photos-json.py
echo.
echo ========================================
pause

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import json
import re
from pathlib import Path
from datetime import datetime
# ==================== 配置区域 ====================
# 请在此处指定你的照片目录和输出文件路径
DEFAULT_PHOTOS_DIR = Path("/www/wwwroot/www.iletter.top/static/img/photos")
DEFAULT_OUTPUT_FILE = Path("/www/wwwroot/www.iletter.top/static/img/photos/photos.json")
# ================================================
# 支持的图片格式
IMAGE_EXTENSIONS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg'}
def natural_sort_key(filename):
"""自然排序键函数,支持数字正确排序"""
return [int(text) if text.isdigit() else text.lower()
for text in re.split(r'(\d+)', filename)]
def scan_photos(photos_dir):
"""扫描照片目录,返回按修改时间排序的文件名列表(最新的在最前面)"""
if not photos_dir.exists():
print(f"❌ 目录不存在: {photos_dir}")
return []
photos = []
for file in photos_dir.iterdir():
if file.is_file() and file.suffix.lower() in IMAGE_EXTENSIONS:
# 排除 JSON 文件本身
if file.name == "photos.json":
continue
# 存储文件名和修改时间戳
photos.append((file.name, file.stat().st_mtime))
# 按修改时间降序排序(最新的在最前面)
photos.sort(key=lambda x: x[1], reverse=True)
# 只返回文件名列表
return [p[0] for p in photos]
def generate_json(photos, output_file):
"""生成 photos.json 文件"""
data = {
"photos": photos,
"total": len(photos),
"lastUpdated": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
# 写入文件
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return len(photos)
def main():
# 确定照片目录和输出文件路径
photos_dir = DEFAULT_PHOTOS_DIR.resolve()
output_file = DEFAULT_OUTPUT_FILE.resolve()
print("🚀 开始扫描照片目录...")
print(f"📁 照片目录: {photos_dir}")
print(f"📝 输出文件: {output_file}")
photos = scan_photos(photos_dir)
if not photos:
print("⚠️ 未找到任何照片文件")
# 仍然生成空的 JSON
generate_json([], output_file)
print(f"✅ 已生成空的 {output_file}")
return
count = generate_json(photos, output_file)
print(f"✅ 成功生成 {output_file}")
print(f"📊 共扫描到 {count} 张照片:")
for i, photo in enumerate(photos, 1):
print(f" {i}. {photo}")
if __name__ == "__main__":
main()

View File

@@ -14,6 +14,28 @@
z-index: 999;
}
/* 人生相册按钮容器 */
.photo-album-btn-container {
position: fixed;
top: 210px;
right: 80px;
z-index: 999;
}
/* 透明桥梁:向左延伸覆盖预览面板区域,确保鼠标滑入时 hover 不中断 */
.guestbook-btn-container::after,
.blog-posts-btn-container::after,
.photo-album-btn-container::after {
content: '';
position: absolute;
right: 80px; /* 从容器的左边缘开始 */
top: 0;
width: 200px; /* 15px 间距 + 380px 预览面板宽度 */
height: 80px; /* 与按钮高度一致 */
background: transparent;
pointer-events: auto;
}
.guestbook-btn {
background: #fff;
@@ -47,6 +69,22 @@
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.photo-album-btn {
background: #fff;
border: 1px solid #e8e8e8;
border-radius: 50%;
width: 80px;
height: 80px;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
}
.btn-label {
font-size: 13px;
color: #666;
@@ -78,7 +116,8 @@
/* Hover 浮动预览面板 */
.guestbook-preview,
.blog-posts-preview {
.blog-posts-preview,
.photo-album-preview {
visibility: hidden;
position: absolute;
top: 0;
@@ -103,7 +142,8 @@
/* 当鼠标悬停在容器上时显示预览面板 */
.guestbook-btn-container:hover .guestbook-preview,
.blog-posts-btn-container:hover .blog-posts-preview {
.blog-posts-btn-container:hover .blog-posts-preview,
.photo-album-btn-container:hover .photo-album-preview {
visibility: visible;
opacity: 1;
transform: translateX(0) scale(1);
@@ -768,6 +808,194 @@
font-size: 10px;
}
/* 人生相册预览面板样式 - 列表模式 */
.photo-album-container {
padding: 10px;
}
.photo-preview-item {
margin-bottom: 10px;
cursor: pointer;
transition: transform 0.3s ease;
}
.photo-preview-item:hover {
transform: scale(1.05);
}
.photo-preview-item img {
width: 100%;
height: 120px;
object-fit: cover;
display: block;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 人生相册瀑布流布局 */
.photo-album-grid {
column-count: 3;
column-gap: 15px;
padding: 20px;
}
@media (max-width: 1200px) {
.photo-album-grid {
column-count: 2;
}
}
@media (max-width: 768px) {
.photo-album-grid {
column-count: 2;
column-gap: 10px;
padding: 10px;
}
}
.photo-album-item {
break-inside: avoid;
margin-bottom: 15px;
position: relative;
overflow: hidden;
border-radius: 12px;
cursor: pointer;
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.photo-album-item:hover {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.photo-album-item img {
width: 100%;
height: auto;
display: block;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.photo-album-item:hover img {
transform: scale(1.1);
}
/* 照片放大弹窗 */
.photo-lightbox {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 10000;
justify-content: center;
align-items: center;
animation: fadeIn 0.3s ease;
}
.photo-lightbox.active {
display: flex;
}
.photo-lightbox img {
max-width: 85%;
max-height: 85%;
object-fit: contain;
border-radius: 8px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
animation: zoomIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
transition: opacity 0.3s ease;
}
.photo-lightbox-close {
position: absolute;
top: 30px;
right: 40px;
font-size: 40px;
color: #fff;
cursor: pointer;
transition: transform 0.3s ease;
z-index: 10001;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
}
.photo-lightbox-close:hover {
transform: rotate(90deg);
background: rgba(255, 255, 255, 0.2);
}
/* 左右切换按钮 */
.photo-lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 50px;
color: #fff;
cursor: pointer;
transition: all 0.3s ease;
z-index: 10001;
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
user-select: none;
}
.photo-lightbox-nav:hover {
background: rgba(255, 255, 255, 0.25);
transform: translateY(-50%) scale(1.1);
}
.photo-lightbox-prev {
left: 40px;
}
.photo-lightbox-next {
right: 40px;
}
/* 图片计数器 */
.photo-lightbox-counter {
position: absolute;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
color: #fff;
font-size: 16px;
background: rgba(0, 0, 0, 0.5);
padding: 8px 20px;
border-radius: 20px;
z-index: 10001;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes zoomIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.guestbook-btn-container {
@@ -780,9 +1008,15 @@
top: 90px;
}
.photo-album-btn-container {
right: 15px;
top: 165px;
}
/* 移动端预览面板调整位置,避免超出屏幕 */
.guestbook-preview,
.blog-posts-preview {
.blog-posts-preview,
.photo-album-preview {
right: auto;
left: calc(100% + 10px);
transform-origin: center left;
@@ -790,7 +1024,8 @@
}
.guestbook-btn-container:hover .guestbook-preview,
.blog-posts-btn-container:hover .blog-posts-preview {
.blog-posts-btn-container:hover .blog-posts-preview,
.photo-album-btn-container:hover .photo-album-preview {
transform: translateX(0) scale(1);
}

BIN
static/img/photos/1 (1).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
static/img/photos/1 (2).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

BIN
static/img/photos/1 (3).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

BIN
static/img/photos/1 (4).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

BIN
static/img/photos/1 (5).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

BIN
static/img/photos/1 (6).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

BIN
static/img/photos/1 (7).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

BIN
static/img/photos/1 (8).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
static/img/photos/1 (9).jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1,19 @@
{
"photos": [
"1 (1).jpg",
"1 (2).jpg",
"1 (3).jpg",
"1 (4).jpg",
"1 (5).jpg",
"1 (6).jpg",
"1 (7).jpg",
"1 (8).jpg",
"1 (9).jpg",
"1 (10).jpg",
"1 (11).jpg",
"1 (12).jpg",
"1 (13).jpg"
],
"total": 13,
"lastUpdated": "2026-05-18 01:57:45"
}

View File

@@ -29,20 +29,6 @@
// 关闭按钮
blogPostsClose.addEventListener('click', closeFullModal);
// 点击背景关闭
blogPostsFullModal.addEventListener('click', function(e) {
if (e.target === blogPostsFullModal) {
closeFullModal();
}
});
// ESC 键关闭
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && blogPostsFullModal.classList.contains('active')) {
closeFullModal();
}
});
// 页面加载时自动加载数据(用于 Hover 预览)
loadBlogPosts(true);
@@ -94,12 +80,13 @@
const result = await response.json();
const posts = result.data.dataSet || [];
const totalPages = result.data.pages || 0;
const totalCount = result.data.count || 0; // 获取总数
// 判断是否还有更多数据
hasMore = currentPage < totalPages;
if (reset) {
renderPosts(posts, true);
renderPosts(posts, true, totalCount);
} else {
appendPosts(posts);
}
@@ -117,7 +104,7 @@
}
// 渲染文章列表
function renderPosts(posts, isReset = false) {
function renderPosts(posts, isReset = false, totalCount = 0) {
let html = '';
if (!posts || posts.length === 0) {
@@ -138,6 +125,18 @@
if (blogPostsList) blogPostsList.innerHTML = html;
if (blogPostsFullBody) blogPostsFullBody.innerHTML = html;
// 更新预览面板标题显示总数
const previewHeader = document.querySelector('#blog-posts-preview .preview-header h3');
if (previewHeader && totalCount > 0) {
const lang = localStorage.getItem('preferred_language') || 'zh';
const translations = {
'zh': typeof translationsZH !== 'undefined' ? translationsZH : {},
'en': typeof translationsEN !== 'undefined' ? translationsEN : {}
};
const titleText = translations[lang]['blog_latest_posts'] || '最新文章';
previewHeader.textContent = `${titleText} (${totalCount})`;
}
}
// 追加文章

View File

@@ -5,7 +5,7 @@
'use strict';
// Flask API 地址
const API_BASE_URL = 'http://localhost:8360';
const API_BASE_URL = 'https://comments.iletter.top';
const PAGE_SIZE = 10;
let currentPage = 1;
@@ -63,20 +63,6 @@
// 关闭按钮
guestbookClose.addEventListener('click', closeFullModal);
// 点击背景关闭
guestbookFullModal.addEventListener('click', function(e) {
if (e.target === guestbookFullModal) {
closeFullModal();
}
});
// ESC 键关闭
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && guestbookFullModal.classList.contains('active')) {
closeFullModal();
}
});
// 预览面板触底加载
if (previewBody) {
previewBody.addEventListener('scroll', handlePreviewScroll);
@@ -217,12 +203,29 @@
guestbookBody.innerHTML = html;
// 同时更新 Hover 预览面板(使用累积的所有留言)
renderPreview(allComments);
renderPreview(allComments, totalCount);
}
// 渲染 Hover 预览面板
function renderPreview(comments) {
if (!previewBody || !comments || comments.length === 0) return;
function renderPreview(comments, totalCount = 0) {
if (!previewBody) return;
if (!comments || comments.length === 0) {
previewBody.innerHTML = '<div class="guestbook-empty">暂无留言,快来抢沙发吧!</div>';
return;
}
// 更新标题显示总数
const previewHeader = document.querySelector('#guestbook-preview .preview-header h3');
if (previewHeader && totalCount > 0) {
const lang = localStorage.getItem('preferred_language') || 'zh';
const translations = {
'zh': typeof translationsZH !== 'undefined' ? translationsZH : {},
'en': typeof translationsEN !== 'undefined' ? translationsEN : {}
};
const titleText = translations[lang]['guestbook_title'] || '留言板';
previewHeader.textContent = `${titleText} (${totalCount})`;
}
let html = '';
comments.forEach(comment => {
@@ -305,7 +308,7 @@
}
// 更新预览面板
renderPreview(allComments);
renderPreview(allComments, allComments.length);
}
// 渲染单个留言项

View File

@@ -73,5 +73,8 @@ const translationsEN = {
// Top-right buttons
btn_guestbook: "Guestbook",
btn_blog_posts: "Blog Posts",
btn_photo_album: "Photo Album",
blog_latest_posts: "Latest Posts",
photo_latest_photos: "Latest Photos",
photo_album_title: "Photo Album",
};

View File

@@ -73,5 +73,8 @@ const translationsZH = {
// 右上角按钮
btn_guestbook: "留言板",
btn_blog_posts: "博客文章",
btn_photo_album: "人生相册",
blog_latest_posts: "最新文章",
photo_latest_photos: "最新照片",
photo_album_title: "人生相册",
};

View File

@@ -32,7 +32,7 @@
let html = "";
memos.forEach((memo) => {
const formattedDate = formatDate(memo.displayTime);
const formattedDate = formatDate(memo.createTime);
const tagsHtml =
memo.tags && memo.tags.length > 0
? `<div class="memo-tags">${memo.tags

242
static/js/photo_album.js Normal file
View File

@@ -0,0 +1,242 @@
// ==================== 人生相册功能 ====================
(function() {
// DOM 元素
const photoAlbumBtn = document.getElementById('photo-album-btn');
const photoAlbumPreview = document.getElementById('photo-album-preview');
const photoAlbumFullModal = document.getElementById('photo-album-full-modal');
const photoAlbumClose = document.getElementById('photo-album-close');
const photoAlbumList = document.querySelector('.photo-album-list');
const photoAlbumBody = document.querySelector('#photo-album-full-modal .photo-album-body');
// 照片数据
let allPhotos = [];
let currentPhotoIndex = 0; // 当前查看的照片索引
const PHOTO_BASE_PATH = './static/img/photos/';
// 初始化
function init() {
if (!photoAlbumBtn || !photoAlbumFullModal) return;
// 点击按钮打开完整弹窗
photoAlbumBtn.addEventListener('click', openFullModal);
// 关闭按钮
photoAlbumClose.addEventListener('click', closeFullModal);
// 页面加载时自动加载照片(用于 Hover 预览)
loadPhotos(true);
}
// 加载照片
async function loadPhotos(reset = false) {
if (reset) {
if (photoAlbumList) photoAlbumList.innerHTML = '<div class="guestbook-loading">正在加载照片...</div>';
if (photoAlbumBody) photoAlbumBody.innerHTML = '<div class="guestbook-loading">正在加载照片...</div>';
}
try {
// 从 JSON 文件获取照片列表
const response = await fetch(`${PHOTO_BASE_PATH}photos.json`);
if (!response.ok) {
throw new Error('Failed to load photos.json');
}
const data = await response.json();
const photoFiles = data.photos || [];
allPhotos = photoFiles.map((filename, index) => ({
id: index + 1,
src: `${PHOTO_BASE_PATH}${encodeURIComponent(filename)}`,
filename: filename
}));
renderPhotos(allPhotos, reset);
} catch (error) {
console.error('加载照片失败:', error);
if (reset) {
const errorMsg = '<div class="guestbook-empty">加载照片失败,请稍后重试</div>';
if (photoAlbumList) photoAlbumList.innerHTML = errorMsg;
if (photoAlbumBody) photoAlbumBody.innerHTML = errorMsg;
}
}
}
// 渲染照片列表
function renderPhotos(photos, isReset = false) {
let html = '';
if (!photos || photos.length === 0) {
html = '<div class="guestbook-empty">暂无照片</div>';
} else {
// 预览面板:显示最新的几张照片
if (isReset && photoAlbumList) {
const previewPhotos = photos.slice(0, 5); // 只显示前5张
let previewHtml = '<div class="photo-album-container">';
previewPhotos.forEach((photo, index) => {
previewHtml += `
<div class="photo-preview-item" onclick="window.openPhotoLightbox(${index})">
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy">
</div>
`;
});
previewHtml += '</div>';
photoAlbumList.innerHTML = previewHtml;
}
// 完整弹窗:瀑布流布局显示所有照片
html = '<div class="photo-album-grid">';
photos.forEach((photo, index) => {
html += `
<div class="photo-album-item" onclick="window.openPhotoLightbox(${index})">
<img src="${photo.src}" alt="照片 ${photo.id}" loading="lazy">
</div>
`;
});
html += '</div>';
}
if (photoAlbumBody) photoAlbumBody.innerHTML = html;
}
// 打开完整弹窗
function openFullModal() {
photoAlbumFullModal.classList.add('active');
document.body.style.overflow = 'hidden';
}
// 关闭完整弹窗
function closeFullModal() {
photoAlbumFullModal.classList.remove('active');
document.body.style.overflow = '';
}
// 打开照片放大查看器
window.openPhotoLightbox = function(photoIndex) {
currentPhotoIndex = photoIndex;
// 检查是否已经存在 lightbox
let lightbox = document.querySelector('.photo-lightbox');
if (!lightbox) {
lightbox = document.createElement('div');
lightbox.className = 'photo-lightbox';
lightbox.innerHTML = `
<span class="photo-lightbox-close">&times;</span>
<span class="photo-lightbox-nav photo-lightbox-prev">&#10094;</span>
<img src="" alt="放大照片">
<span class="photo-lightbox-nav photo-lightbox-next">&#10095;</span>
<div class="photo-lightbox-counter"></div>
`;
document.body.appendChild(lightbox);
// 点击关闭按钮
lightbox.querySelector('.photo-lightbox-close').addEventListener('click', closePhotoLightbox);
// 点击背景关闭
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) {
closePhotoLightbox();
}
});
// 上一张按钮
lightbox.querySelector('.photo-lightbox-prev').addEventListener('click', function(e) {
e.stopPropagation();
showPreviousPhoto();
});
// 下一张按钮
lightbox.querySelector('.photo-lightbox-next').addEventListener('click', function(e) {
e.stopPropagation();
showNextPhoto();
});
// 键盘事件
document.addEventListener('keydown', handleLightboxKeydown);
}
// 更新图片显示
updateLightboxImage();
lightbox.classList.add('active');
};
// 更新 Lightbox 图片
function updateLightboxImage() {
const lightbox = document.querySelector('.photo-lightbox');
if (!lightbox || !allPhotos[currentPhotoIndex]) return;
const img = lightbox.querySelector('img');
const counter = lightbox.querySelector('.photo-lightbox-counter');
// 淡出效果
img.style.opacity = '0';
setTimeout(() => {
img.src = allPhotos[currentPhotoIndex].src;
img.alt = `照片 ${currentPhotoIndex + 1}`;
// 更新计数器
if (counter) {
counter.textContent = `${currentPhotoIndex + 1} / ${allPhotos.length}`;
}
// 淡入效果
img.onload = () => {
img.style.opacity = '1';
};
}, 150);
}
// 显示上一张照片
function showPreviousPhoto() {
if (currentPhotoIndex > 0) {
currentPhotoIndex--;
} else {
currentPhotoIndex = allPhotos.length - 1; // 循环到最后一张
}
updateLightboxImage();
}
// 显示下一张照片
function showNextPhoto() {
if (currentPhotoIndex < allPhotos.length - 1) {
currentPhotoIndex++;
} else {
currentPhotoIndex = 0; // 循环到第一张
}
updateLightboxImage();
}
// 处理键盘事件
function handleLightboxKeydown(e) {
if (e.key === 'ArrowLeft') {
showPreviousPhoto();
} else if (e.key === 'ArrowRight') {
showNextPhoto();
} else if (e.key === 'Escape') {
closePhotoLightbox();
}
}
// 关闭照片放大查看器
function closePhotoLightbox() {
const lightbox = document.querySelector('.photo-lightbox');
if (lightbox) {
lightbox.classList.remove('active');
// 移除键盘事件监听器
document.removeEventListener('keydown', handleLightboxKeydown);
setTimeout(() => {
lightbox.remove();
}, 300);
}
}
// DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();