798 lines
25 KiB
HTML
798 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||
<meta http-equiv="Pragma" content="no-cache">
|
||
<meta http-equiv="Expires" content="0">
|
||
<title>相册查看器</title>
|
||
<style>
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||
background: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
/* ========== 登录弹窗 ========== */
|
||
.login-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.8);
|
||
z-index: 10000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
backdrop-filter: blur(5px);
|
||
}
|
||
|
||
.login-overlay.hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
.login-box {
|
||
background: white;
|
||
padding: 30px;
|
||
border-radius: 16px;
|
||
width: 90%;
|
||
max-width: 360px;
|
||
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
||
}
|
||
|
||
.login-title {
|
||
text-align: center;
|
||
font-size: 22px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
color: #333;
|
||
}
|
||
|
||
.login-subtitle {
|
||
text-align: center;
|
||
font-size: 14px;
|
||
color: #999;
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.login-input {
|
||
width: 100%;
|
||
padding: 14px 16px;
|
||
margin-bottom: 12px;
|
||
border: 1px solid #e0e0e0;
|
||
border-radius: 10px;
|
||
font-size: 16px;
|
||
transition: border-color 0.2s;
|
||
-webkit-appearance: none;
|
||
}
|
||
|
||
.login-input:focus {
|
||
outline: none;
|
||
border-color: #007aff;
|
||
}
|
||
|
||
.login-btn {
|
||
width: 100%;
|
||
padding: 14px;
|
||
background: #007aff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 10px;
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background 0.2s;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.login-btn:hover {
|
||
background: #0056cc;
|
||
}
|
||
|
||
.login-btn:disabled {
|
||
background: #ccc;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.sso-login-btn {
|
||
width: 100%;
|
||
padding: 12px;
|
||
margin-top: 12px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 15px;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
}
|
||
|
||
.sso-login-btn:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.login-divider {
|
||
display: flex;
|
||
align-items: center;
|
||
margin: 12px 0;
|
||
color: #aaa;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.login-divider::before,
|
||
.login-divider::after {
|
||
content: '';
|
||
flex: 1;
|
||
border-bottom: 1px solid #ddd;
|
||
}
|
||
|
||
.login-divider::before { margin-right: 8px; }
|
||
.login-divider::after { margin-left: 8px; }
|
||
|
||
.login-error {
|
||
color: #ff3b30;
|
||
font-size: 14px;
|
||
margin-top: 12px;
|
||
text-align: center;
|
||
display: none;
|
||
}
|
||
|
||
.login-error.show {
|
||
display: block;
|
||
}
|
||
|
||
/* ========== 顶部导航 ========== */
|
||
.header {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 50px;
|
||
background: #fff;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 16px;
|
||
z-index: 100;
|
||
}
|
||
|
||
.back-btn {
|
||
display: none;
|
||
padding: 8px 12px;
|
||
background: #007aff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.back-btn.visible {
|
||
display: block;
|
||
}
|
||
|
||
.title {
|
||
flex: 1;
|
||
text-align: center;
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
color: #333;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.user-info {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 13px;
|
||
color: #666;
|
||
}
|
||
|
||
.user-info.visible {
|
||
display: flex;
|
||
}
|
||
|
||
.logout-btn {
|
||
padding: 4px 10px;
|
||
background: #ff3b30;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ========== 主内容区 ========== */
|
||
.content {
|
||
padding: 66px 12px 20px;
|
||
}
|
||
|
||
/* 网格布局:每行3个 */
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 12px;
|
||
}
|
||
|
||
/* 缩略图项 */
|
||
.item {
|
||
position: relative;
|
||
aspect-ratio: 2/3;
|
||
background: #e0e0e0;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.item img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
.item img.lazy {
|
||
opacity: 0;
|
||
}
|
||
|
||
.item img.loaded {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 文件名标签 */
|
||
.item-filename {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 6px 8px;
|
||
background: linear-gradient(to bottom, rgba(0,0,0,0.6), transparent);
|
||
color: white;
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
z-index: 2;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 相册图标 */
|
||
.album-icon {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
}
|
||
|
||
.album-icon svg {
|
||
width: 50%;
|
||
height: 50%;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* 相册封面 */
|
||
.album-cover {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
/* 相册名称 */
|
||
.item-name {
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 20px 8px 8px;
|
||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
||
color: white;
|
||
font-size: 12px;
|
||
text-align: center;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* ========== 图片查看器 ========== */
|
||
.lightbox {
|
||
display: none;
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: rgba(0,0,0,0.95);
|
||
z-index: 1000;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.lightbox.active {
|
||
display: flex;
|
||
}
|
||
|
||
.lightbox img {
|
||
max-width: 100%;
|
||
max-height: 100%;
|
||
object-fit: contain;
|
||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||
user-select: none;
|
||
-webkit-user-drag: none;
|
||
}
|
||
|
||
.lightbox-close {
|
||
position: absolute;
|
||
top: 20px;
|
||
right: 20px;
|
||
width: 40px;
|
||
height: 40px;
|
||
background: rgba(255,255,255,0.2);
|
||
border: none;
|
||
border-radius: 50%;
|
||
color: white;
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
/* ========== 加载/空状态 ========== */
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40px;
|
||
color: #999;
|
||
}
|
||
|
||
.empty {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #999;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 64px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.login-required {
|
||
text-align: center;
|
||
padding: 60px 20px;
|
||
color: #999;
|
||
}
|
||
|
||
.login-required-icon {
|
||
font-size: 64px;
|
||
margin-bottom: 16px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- 登录弹窗 -->
|
||
<div class="login-overlay" id="loginOverlay">
|
||
<div class="login-box">
|
||
<div class="login-title">🔒 相册查看器</div>
|
||
<div class="login-subtitle">请先登录后继续</div>
|
||
<input type="text" class="login-input" id="username" placeholder="用户名" autocomplete="username">
|
||
<input type="password" class="login-input" id="password" placeholder="密码" autocomplete="current-password">
|
||
<button class="login-btn" id="loginBtn" onclick="doLogin()">登录</button>
|
||
<div class="login-divider">或</div>
|
||
<button class="sso-login-btn" onclick="doSSOLogin()">🔐 SSO 统一登录</button>
|
||
<div class="login-error" id="loginError"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 顶部导航 -->
|
||
<div class="header">
|
||
<button class="back-btn" onclick="goBack()">← 返回</button>
|
||
<div class="title">相册首页</div>
|
||
<div class="user-info" id="userInfo">
|
||
<span id="usernameDisplay"></span>
|
||
<button class="logout-btn" onclick="doLogout()">退出</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容区 -->
|
||
<div class="content">
|
||
<div class="grid" id="grid">
|
||
<div class="loading">加载中...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图片查看器 -->
|
||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||
<button class="lightbox-close" onclick="event.stopPropagation(); closeLightbox()">×</button>
|
||
<img id="lightbox-img" src="" alt="" onclick="event.stopPropagation()">
|
||
</div>
|
||
|
||
<script>
|
||
// ========== 状态 ==========
|
||
let isAuthenticated = false;
|
||
let pathStack = [''];
|
||
let currentImages = [];
|
||
let currentImageIndex = -1;
|
||
let touchStartX = 0;
|
||
let touchStartY = 0;
|
||
let touchEndX = 0;
|
||
let isSwiping = false;
|
||
|
||
// ========== 初始化 ==========
|
||
document.addEventListener('DOMContentLoaded', async () => {
|
||
// 检查登录状态
|
||
await checkAuth();
|
||
|
||
if (isAuthenticated) {
|
||
loadAlbum('');
|
||
setupSwipeNavigation();
|
||
}
|
||
|
||
// 回车键登录
|
||
document.getElementById('password').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') doLogin();
|
||
});
|
||
});
|
||
|
||
// ========== 认证相关 ==========
|
||
async function checkAuth() {
|
||
try {
|
||
const response = await fetch('/api/auth/status', {
|
||
credentials: 'include', // 携带 cookie
|
||
cache: 'no-cache' // 禁用缓存
|
||
});
|
||
const data = await response.json();
|
||
|
||
isAuthenticated = data.authenticated;
|
||
|
||
if (isAuthenticated) {
|
||
showMainUI(data.user);
|
||
} else {
|
||
showLoginUI();
|
||
}
|
||
} catch (error) {
|
||
console.error('检查登录状态失败:', error);
|
||
showLoginUI();
|
||
}
|
||
}
|
||
|
||
async function doLogin() {
|
||
const username = document.getElementById('username').value.trim();
|
||
const password = document.getElementById('password').value.trim();
|
||
const btn = document.getElementById('loginBtn');
|
||
const errorDiv = document.getElementById('loginError');
|
||
|
||
if (!username || !password) {
|
||
errorDiv.textContent = '请输入用户名和密码';
|
||
errorDiv.classList.add('show');
|
||
return;
|
||
}
|
||
|
||
btn.disabled = true;
|
||
btn.textContent = '登录中...';
|
||
|
||
try {
|
||
const response = await fetch('/api/auth/login', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
credentials: 'include',
|
||
body: JSON.stringify({ username, password })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
isAuthenticated = true;
|
||
showMainUI(data.user);
|
||
loadAlbum('');
|
||
setupSwipeNavigation();
|
||
} else {
|
||
errorDiv.textContent = data.error || '登录失败';
|
||
errorDiv.classList.add('show');
|
||
}
|
||
} catch (error) {
|
||
errorDiv.textContent = '网络错误: ' + error.message;
|
||
errorDiv.classList.add('show');
|
||
console.error('登录错误:', error);
|
||
} finally {
|
||
btn.disabled = false;
|
||
btn.textContent = '登录';
|
||
}
|
||
}
|
||
|
||
async function doLogout() {
|
||
try {
|
||
// 调后端同时清除本地 Session 和 SSO Cookie
|
||
await fetch('/api/auth/sso-logout', {
|
||
method: 'POST',
|
||
credentials: 'include'
|
||
});
|
||
} catch (e) {}
|
||
|
||
isAuthenticated = false;
|
||
location.reload();
|
||
}
|
||
|
||
function doSSOLogin() {
|
||
// 跳转到 SSO 中心,携带 redirect 参数
|
||
const redirect = window.location.origin;
|
||
window.location.href = 'http://129.211.173.87:9000/?redirect=' + encodeURIComponent(redirect + '/');
|
||
}
|
||
|
||
function showLoginUI() {
|
||
document.getElementById('loginOverlay').classList.remove('hidden');
|
||
document.getElementById('userInfo').classList.remove('visible');
|
||
document.getElementById('grid').innerHTML = `
|
||
<div class="login-required" style="grid-column: span 3;">
|
||
<div class="login-required-icon">🔒</div>
|
||
<div>请先登录查看相册</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function showMainUI(user) {
|
||
document.getElementById('loginOverlay').classList.add('hidden');
|
||
document.getElementById('userInfo').classList.add('visible');
|
||
document.getElementById('usernameDisplay').textContent = user.username;
|
||
// 清空输入
|
||
document.getElementById('username').value = '';
|
||
document.getElementById('password').value = '';
|
||
document.getElementById('loginError').classList.remove('show');
|
||
}
|
||
|
||
// ========== API 请求封装(自动处理认证)==========
|
||
async function authFetch(url, options = {}) {
|
||
options.credentials = 'include'; // 携带 cookie
|
||
|
||
const response = await fetch(url, options);
|
||
|
||
// 401 未授权,显示登录
|
||
if (response.status === 401) {
|
||
isAuthenticated = false;
|
||
showLoginUI();
|
||
throw new Error('未登录');
|
||
}
|
||
|
||
return response;
|
||
}
|
||
|
||
// ========== 相册功能(保持原有逻辑)==========
|
||
async function loadAlbum(path) {
|
||
if (!isAuthenticated) return;
|
||
|
||
const grid = document.getElementById('grid');
|
||
grid.innerHTML = '<div class="loading">加载中...</div>';
|
||
|
||
try {
|
||
const response = await authFetch(`/api/albums/${path}`);
|
||
const data = await response.json();
|
||
|
||
if (data.error) {
|
||
grid.innerHTML = `<div class="empty">加载失败: ${data.error}</div>`;
|
||
return;
|
||
}
|
||
|
||
document.querySelector('.title').textContent = data.name || '相册';
|
||
|
||
const backBtn = document.querySelector('.back-btn');
|
||
if (pathStack.length > 1) {
|
||
backBtn.classList.add('visible');
|
||
} else {
|
||
backBtn.classList.remove('visible');
|
||
}
|
||
|
||
renderGrid(data);
|
||
|
||
} catch (error) {
|
||
if (error.message !== '未登录') {
|
||
grid.innerHTML = `<div class="empty">加载失败,请刷新重试</div>`;
|
||
}
|
||
}
|
||
}
|
||
|
||
function renderGrid(data) {
|
||
const grid = document.getElementById('grid');
|
||
grid.innerHTML = '';
|
||
|
||
const items = [...data.albums, ...data.images];
|
||
currentImages = data.images || [];
|
||
|
||
if (items.length === 0) {
|
||
grid.innerHTML = `
|
||
<div class="empty" style="grid-column: span 3;">
|
||
<div class="empty-icon">🖼️</div>
|
||
<div>暂无内容</div>
|
||
</div>
|
||
`;
|
||
return;
|
||
}
|
||
|
||
items.forEach(item => {
|
||
const div = document.createElement('div');
|
||
div.className = 'item';
|
||
|
||
if (item.type === 'album') {
|
||
const coverHtml = item.cover
|
||
? `<img class="album-cover lazy" data-src="${item.cover}" alt="${item.name}">`
|
||
: `<div class="album-icon">
|
||
<svg viewBox="0 0 24 24" fill="white">
|
||
<path d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z"/>
|
||
<circle cx="12" cy="13" r="3"/>
|
||
</svg>
|
||
</div>`;
|
||
|
||
div.innerHTML = `
|
||
${coverHtml}
|
||
<div class="item-name">${item.name}</div>
|
||
`;
|
||
div.onclick = () => openAlbum(item.path);
|
||
|
||
} else {
|
||
const thumbUrl = item.thumbnail || `/api/image/${item.path}`;
|
||
const imageIndex = data.images.indexOf(item);
|
||
div.innerHTML = `
|
||
<div class="item-filename">${item.name}</div>
|
||
<img class="lazy" data-src="${thumbUrl}" alt="${item.name}" onclick="showImage(${imageIndex})">
|
||
`;
|
||
}
|
||
|
||
grid.appendChild(div);
|
||
});
|
||
|
||
setupLazyLoad();
|
||
}
|
||
|
||
function openAlbum(path) {
|
||
pathStack.push(path);
|
||
loadAlbum(path);
|
||
}
|
||
|
||
function goBack() {
|
||
if (pathStack.length > 1) {
|
||
pathStack.pop();
|
||
loadAlbum(pathStack[pathStack.length - 1]);
|
||
}
|
||
}
|
||
|
||
function showImage(index) {
|
||
event.stopPropagation();
|
||
if (index < 0 || index >= currentImages.length) return;
|
||
|
||
currentImageIndex = index;
|
||
const image = currentImages[index];
|
||
|
||
const lightbox = document.getElementById('lightbox');
|
||
const img = document.getElementById('lightbox-img');
|
||
img.src = `/api/image/${image.path}`;
|
||
lightbox.classList.add('active');
|
||
document.body.style.overflow = 'hidden';
|
||
}
|
||
|
||
function navigateImage(direction) {
|
||
const newIndex = currentImageIndex + direction;
|
||
|
||
if (newIndex < 0 || newIndex >= currentImages.length) {
|
||
const img = document.getElementById('lightbox-img');
|
||
img.style.transform = direction > 0 ? 'translateX(-20px)' : 'translateX(20px)';
|
||
setTimeout(() => {
|
||
img.style.transform = 'translateX(0)';
|
||
}, 150);
|
||
return;
|
||
}
|
||
|
||
currentImageIndex = newIndex;
|
||
const image = currentImages[currentImageIndex];
|
||
const img = document.getElementById('lightbox-img');
|
||
|
||
img.style.opacity = '0.5';
|
||
img.style.transform = direction > 0 ? 'translateX(-50px)' : 'translateX(50px)';
|
||
|
||
setTimeout(() => {
|
||
img.src = `/api/image/${image.path}`;
|
||
img.style.transition = 'none';
|
||
img.style.transform = direction > 0 ? 'translateX(50px)' : 'translateX(-50px)';
|
||
|
||
setTimeout(() => {
|
||
img.style.transition = 'all 0.2s ease';
|
||
img.style.opacity = '1';
|
||
img.style.transform = 'translateX(0)';
|
||
}, 50);
|
||
}, 150);
|
||
}
|
||
|
||
function closeLightbox() {
|
||
const lightbox = document.getElementById('lightbox');
|
||
lightbox.classList.remove('active');
|
||
document.body.style.overflow = '';
|
||
}
|
||
|
||
function setupSwipeNavigation() {
|
||
const lightbox = document.getElementById('lightbox');
|
||
|
||
lightbox.addEventListener('touchstart', (e) => {
|
||
touchStartX = e.touches[0].clientX;
|
||
touchStartY = e.touches[0].clientY;
|
||
isSwiping = false;
|
||
}, { passive: true });
|
||
|
||
lightbox.addEventListener('touchmove', (e) => {
|
||
if (!lightbox.classList.contains('active')) return;
|
||
|
||
touchEndX = e.touches[0].clientX;
|
||
const diffX = touchStartX - touchEndX;
|
||
const diffY = Math.abs(e.touches[0].clientY - touchStartY);
|
||
|
||
if (Math.abs(diffX) > diffY && Math.abs(diffX) > 10) {
|
||
isSwiping = true;
|
||
}
|
||
}, { passive: true });
|
||
|
||
lightbox.addEventListener('touchend', (e) => {
|
||
if (!lightbox.classList.contains('active') || !isSwiping) return;
|
||
|
||
const diffX = touchStartX - touchEndX;
|
||
const threshold = 50;
|
||
|
||
if (Math.abs(diffX) > threshold) {
|
||
if (diffX > 0) {
|
||
navigateImage(1);
|
||
} else {
|
||
navigateImage(-1);
|
||
}
|
||
}
|
||
|
||
isSwiping = false;
|
||
}, { passive: true });
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (!lightbox.classList.contains('active')) return;
|
||
|
||
if (e.key === 'ArrowLeft') navigateImage(-1);
|
||
else if (e.key === 'ArrowRight') navigateImage(1);
|
||
else if (e.key === 'Escape') closeLightbox();
|
||
});
|
||
}
|
||
|
||
function setupLazyLoad() {
|
||
const images = document.querySelectorAll('img.lazy');
|
||
|
||
const imageObserver = new IntersectionObserver((entries, observer) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
const img = entry.target;
|
||
img.src = img.dataset.src;
|
||
img.onload = () => img.classList.add('loaded');
|
||
img.classList.remove('lazy');
|
||
observer.unobserve(img);
|
||
}
|
||
});
|
||
}, {
|
||
rootMargin: '50px 0px',
|
||
threshold: 0.01
|
||
});
|
||
|
||
images.forEach(img => imageObserver.observe(img));
|
||
}
|
||
|
||
window.onpopstate = function(event) {
|
||
if (pathStack.length > 1) {
|
||
pathStack.pop();
|
||
loadAlbum(pathStack[pathStack.length - 1]);
|
||
}
|
||
};
|
||
</script>
|
||
</body>
|
||
</html>
|