Files
img-bed/public/api/i/gallery-meow/script.js
2026-01-01 23:55:35 +08:00

344 lines
9.3 KiB
JavaScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
let allImages = [];
let visibleImages = [];
let currentIndex = 0;
let imageObserver = null;
const expandedYears = new Set();
const monthNames = {
"01": "一月",
"02": "二月",
"03": "三月",
"04": "四月",
"05": "五月",
"06": "六月",
"07": "七月",
"08": "八月",
"09": "九月",
10: "十月",
11: "十一月",
12: "十二月",
};
// 初始化懒加载观察器
function initLazyLoading() {
if (imageObserver) return;
imageObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.dataset.src;
if (src) {
img.src = src;
img.classList.add("loaded");
imageObserver.unobserve(img);
}
}
});
},
{
rootMargin: "50px 0px",
threshold: 0.1,
},
);
}
// 观察图片元素
function observeImage(img) {
if (imageObserver) {
imageObserver.observe(img);
}
}
async function discoverImages() {
const loading = document.getElementById("loading");
const progressText = document.getElementById("loadingProgress");
const content = document.getElementById("content");
try {
progressText.textContent = "正在加载图片列表...";
const response = await fetch("/api/i/images.json");
if (!response.ok) throw new Error("Failed to load images.json");
allImages = await response.json();
// Sort by date descending
allImages.sort((a, b) => b.date.localeCompare(a.date));
loading.classList.add("hidden");
content.classList.remove("hidden");
document.getElementById("totalCount").textContent =
allImages.length.toLocaleString();
const _years = [...new Set(allImages.map((img) => img.year))];
document.getElementById("yearCount").textContent = _years.length;
const months = [
...new Set(allImages.map((img) => `${img.year}-${img.month}`)),
];
document.getElementById("monthCount").textContent = months.length;
populateFilters();
// 初始化懒加载
initLazyLoading();
// 默认只展开最近的年份
const years = [...new Set(allImages.map((img) => img.year))]
.sort()
.reverse();
if (years.length > 0) {
expandedYears.add(years[0]); // 展开最新年份
}
renderGallery();
} catch (error) {
console.error("Error loading gallery:", error);
progressText.textContent = "加载失败,请检查网络或稍后重试";
progressText.style.color = "#ef4444";
}
}
function populateFilters() {
const years = [...new Set(allImages.map((img) => img.year))].sort().reverse();
const yearSelect = document.getElementById("yearFilter");
years.forEach((year) => {
const option = document.createElement("option");
option.value = year;
option.textContent = `${year}`;
yearSelect.appendChild(option);
});
updateMonthFilter();
document
.getElementById("yearFilter")
.addEventListener("change", updateMonthFilter);
}
function updateMonthFilter() {
const year = document.getElementById("yearFilter").value;
const monthSelect = document.getElementById("monthFilter");
monthSelect.innerHTML = '<option value="">全部月份</option>';
if (year) {
const months = [
...new Set(
allImages.filter((img) => img.year === year).map((img) => img.month),
),
].sort();
months.forEach((month) => {
const option = document.createElement("option");
option.value = month;
option.textContent = `${Number.parseInt(month)}`;
monthSelect.appendChild(option);
});
}
filterGallery();
}
function filterGallery() {
const year = document.getElementById("yearFilter").value;
const month = document.getElementById("monthFilter").value;
const search = document.getElementById("searchInput").value.toLowerCase();
visibleImages = allImages.filter((img) => {
if (year && img.year !== year) return false;
if (month && img.month !== month) return false;
if (search && !img.filename.toLowerCase().includes(search)) return false;
return true;
});
renderGallery();
}
function renderGallery() {
const timeline = document.getElementById("timeline");
timeline.innerHTML = "";
if (visibleImages.length === 0) {
timeline.innerHTML =
'<div style="text-align: center; padding: 4rem; color: var(--text-gray);">没有找到匹配的图片</div>';
return;
}
const byYear = {};
visibleImages.forEach((img) => {
if (!byYear[img.year]) byYear[img.year] = {};
if (!byYear[img.year][img.month]) byYear[img.year][img.month] = [];
byYear[img.year][img.month].push(img);
});
Object.keys(byYear)
.sort()
.reverse()
.forEach((year) => {
const yearEl = document.createElement("div");
yearEl.className = "timeline-year";
yearEl.dataset.year = year;
const yearCount = Object.values(byYear[year]).reduce(
(sum, imgs) => sum + imgs.length,
0,
);
const isExpanded = expandedYears.has(year);
const toggleIcon = isExpanded ? "▼" : "▶";
yearEl.innerHTML = `
<div class="timeline-year-marker">${year.slice(-2)}</div>
<div class="timeline-year-header">
<h2>${year}年</h2>
<span class="count">${yearCount} 张照片</span>
<span class="toggle-icon">${toggleIcon}</span>
</div>
`;
const yearHeader = yearEl.querySelector(".timeline-year-header");
yearHeader?.addEventListener("click", () => toggleYear(year));
if (isExpanded) {
Object.keys(byYear[year])
.sort()
.reverse()
.forEach((month) => {
const monthImages = byYear[year][month];
const monthEl = document.createElement("div");
monthEl.className = "month-group";
monthEl.dataset.month = month;
monthEl.innerHTML = `
<div class="month-header">
<span class="icon">📆</span>
<span>${monthNames[month] || `${month}`}</span>
<span style="color: var(--text-gray); font-size: 0.875rem;">(${monthImages.length} 张)</span>
</div>
<div class="gallery-masonry"></div>
`;
const masonry = monthEl.querySelector(".gallery-masonry");
monthImages.forEach((img) => {
const card = document.createElement("div");
card.className = "image-card";
card.dataset.filename = img.filename;
card.dataset.date = img.date;
card.onclick = () => openLightbox(img);
// 创建包装器
const wrapper = document.createElement("div");
wrapper.className = "wrapper";
// 创建占位符
const placeholder = document.createElement("div");
placeholder.className = "image-placeholder";
placeholder.innerHTML = "📷";
// 创建图片元素(懒加载)
const imgElement = document.createElement("img");
imgElement.dataset.src = img.url;
imgElement.alt = img.filename;
imgElement.className = "lazy-image";
imgElement.onload = () => {
placeholder.style.display = "none";
imgElement.classList.add("loaded");
};
imgElement.onerror = () => {
placeholder.innerHTML = "❌";
};
observeImage(imgElement);
// 创建覆盖层
const overlay = document.createElement("div");
overlay.className = "overlay";
overlay.innerHTML = '<span class="icon">🔍</span>';
// 组装包装器
wrapper.appendChild(placeholder);
wrapper.appendChild(imgElement);
wrapper.appendChild(overlay);
// 创建信息区域
const info = document.createElement("div");
info.className = "info";
info.innerHTML = `
<div class="filename" title="${img.filename}">${img.filename}</div>
<div class="date">${img.date}</div>
`;
// 组装卡片
card.appendChild(wrapper);
card.appendChild(info);
masonry.appendChild(card);
});
yearEl.appendChild(monthEl);
});
}
timeline.appendChild(yearEl);
});
}
// 切换年份展开/折叠
function toggleYear(year) {
if (expandedYears.has(year)) {
expandedYears.delete(year);
} else {
expandedYears.add(year);
}
renderGallery();
}
function openLightbox(img) {
const lightbox = document.getElementById("lightbox");
document.getElementById("lightboxImage").src = img.url;
document.getElementById("lightboxFilename").textContent = img.filename;
document.getElementById("lightboxMeta").textContent = img.date;
currentIndex = visibleImages.indexOf(img);
lightbox.classList.add("active");
}
function closeLightbox() {
document.getElementById("lightbox").classList.remove("active");
}
function navigateImage(direction) {
currentIndex += direction;
if (currentIndex < 0) currentIndex = visibleImages.length - 1;
if (currentIndex >= visibleImages.length) currentIndex = 0;
const img = visibleImages[currentIndex];
if (img) {
openLightbox(img);
}
}
document.addEventListener("keydown", (e) => {
if (document.getElementById("lightbox").classList.contains("active")) {
if (e.key === "Escape") closeLightbox();
if (e.key === "ArrowLeft") navigateImage(-1);
if (e.key === "ArrowRight") navigateImage(1);
}
});
document.getElementById("lightbox").addEventListener("click", (e) => {
if (e.target.id === "lightbox") closeLightbox();
});
// 兼容内联事件处理器index.html 里的 onchange/onclick
window.filterGallery = filterGallery;
window.closeLightbox = closeLightbox;
window.navigateImage = navigateImage;
window.toggleYear = toggleYear;
discoverImages();