一个纯HTML编写的图片浏览静态页面,支持PC和H5端适配。页面提供网格/列表两种视图模式,包含搜索、排序、图片信息展示和下载功能。
目录
1、图片墙
工作中经常需要分享图片,特制作一个图片墙浏览页面。以草图和大纲形式,喂给AI辅助完成页面框架搭建,经过不断调试和优化,最终效果还算过得去。纯前端,供有需要的同学参考。
2、PC端预览

- 网格视图、列表视图


3、H5端预览

-
列表视图、网格视图


4、HTML代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片浏览器</title>
<!-- 引入 Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* 自定义滚动条 */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* 图片卡片悬停效果 */
.image-card {
transition: all 0.3s ease;
}
.image-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.15);
}
/* 列表视图样式调整 */
.list-view .image-card {
flex-direction: row;
align-items: center;
height: auto;
}
.list-view .image-card img {
width: 100px;
height: 100px;
object-fit: cover;
margin-right: 20px;
}
.list-view .card-content {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
/* 加载动画 */
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3b82f6;
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50 text-gray-800 font-sans min-h-screen flex flex-col">
<!-- 头部导航 -->
<header class="bg-white shadow-sm sticky top-0 z-50">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between">
<div class="flex items-center gap-1">
<i class="fa-solid fa-images text-blue-600 text-xl"></i>
<h1 class="text-xl font-bold text-gray-900">图库</h1>
</div>
<div class="flex items-center gap-4">
<!-- 搜索框 hidden -->
<div class="relative md:block">
<input type="text" id="searchInput" placeholder="搜索图片..." class="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent w-40 transition-all">
<i class="fa-solid fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
<!-- 视图切换按钮 -->
<button id="toggleViewBtn" class="flex items-center gap-2 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
<i id="viewIcon" class="fa-solid fa-list"></i>
<span id="viewText">列表</span>
</button>
</div>
</div>
</header>
<!-- 主内容区域 -->
<main class="flex-grow max-w-7xl mx-auto px-2 sm:px-6 lg:px-8 py-3 w-full">
<!-- 统计信息 -->
<div class="mb-3 flex justify-between items-center text-sm text-gray-500">
<span id="imageCount">共 0 张图片</span>
<div class="flex gap-2">
<button onclick="sortfileList('date')" class="hover:text-blue-600 transition-colors"><i class="fa-solid fa-sort-amount-down"></i> 按日期</button>
<button onclick="sortfileList('name')" class="hover:text-blue-600 transition-colors"><i class="fa-solid fa-sort-alpha-down"></i> 按名称</button>
</div>
</div>
<!-- 图片容器 -->
<div id="galleryContainer" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3 gap-2 transition-all duration-300">
<!-- 图片将通过 JS 动态插入这里 -->
</div>
<!-- 空状态提示 -->
<div id="emptyState" class="hidden flex-col items-center justify-center py-20 text-gray-400">
<i class="fa-regular fa-image text-6xl mb-4"></i>
<p>没有找到相关图片</p>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-white border-t mt-auto py-6">
<div class="max-w-7xl mx-auto px-4 text-center text-gray-500 text-sm">Copyright © 2026.</div>
<div class="max-w-7xl mx-auto px-4 text-center text-gray-500 text-sm">狂龙骄子 All Rights Reserved.</div>
</footer>
<!-- Toast 通知容器 -->
<div id="toastContainer" class="fixed bottom-5 right-5 z-50 flex flex-col gap-2"></div>
<script>
// 模拟图片数据
const initialfileList = [
"https://assets.699pic.com/public/web/images/500/709/879.jpg!detail.v1",
"https://assets.699pic.com/public/web/images/500/534/517.jpg!detail.v1",
"https://assets.699pic.com/public/web/images/701/264/819.jpg!detail.v1",
"https://assets.699pic.com/public/web/images/701/400/464.jpg!detail.v1",
"https://assets.699pic.com/public/web/images/701/226/206.jpg!detail.v1",
];
let fileList = [...initialfileList];
let fileInfoArray = [];
let isGridView = true; // 默认网格视图
let titleSortType = 'ASC';
let dateSortType = 'ASC';
// DOM 元素
const galleryContainer = document.getElementById('galleryContainer');
const toggleViewBtn = document.getElementById('toggleViewBtn');
const viewIcon = document.getElementById('viewIcon');
const viewText = document.getElementById('viewText');
const searchInput = document.getElementById('searchInput');
const imageCount = document.getElementById('imageCount');
const emptyState = document.getElementById('emptyState');
// 初始化
document.addEventListener('DOMContentLoaded', async () => {
// 并行获取所有图片的详细信息
fileInfoArray = await Promise.all(
initialfileList.map(file => getImageDetails(file).catch(() => null))
);
let imageList = [];
for(let i=0; i<initialfileList.length; i++) {
const url = initialfileList[i];
const imageInfo = fileInfoArray[i];
initialfileList[i] = {
title: getFilenameFromUrl(url),
url: url,
lastModified: imageInfo ? formatLastModified(imageInfo.lastModified) : null,
size: imageInfo ? imageInfo.sizeKB : null,
width: imageInfo ? imageInfo.width : null,
height: imageInfo ? imageInfo.height : null,
};
}
fileList = [...initialfileList];
renderfileList();
// 自动触发一次视图切换以初始化状态(可选,这里直接渲染默认状态)
// toggleViewBtn.click();
// 绑定事件
toggleViewBtn.addEventListener('click', switchView);
searchInput.addEventListener('input', handleSearch);
});
// 渲染图片列表
function renderfileList() {
galleryContainer.innerHTML = '';
if (fileList.length === 0) {
emptyState.classList.remove('hidden');
emptyState.classList.add('flex');
imageCount.textContent = `共 0 张图片`;
return;
} else {
emptyState.classList.add('hidden');
emptyState.classList.remove('flex');
}
imageCount.textContent = `共 ${fileList.length} 张图片`;
fileList.forEach((img, index) => {
const card = document.createElement('div');
card.className = `image-card bg-white rounded-xl overflow-hidden shadow-sm border border-gray-100 flex flex-col ${!isGridView ? 'list-view-item' : ''}`;
// 构建卡片 HTML
card.innerHTML = `
<div class="relative group overflow-hidden ${isGridView ? 'h-48' : 'w-24 h-24 flex-shrink-0'}">
<img src="${img.url}" alt="${img.title}" class="w-full h-full object-scale transition-transform duration-300 group-hover:scale-110" loading="lazy">
<div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-all duration-300"></div>
</div>
<div class="p-2 flex flex-col flex-grow ${!isGridView ? 'justify-center' : ''} overflow-hidden">
<div class="flex justify-between items-start mb-2">
<h3 class="font-semibold text-gray-800 truncate pr-2" title="${img.title}">${img.title}</h3>
</div>
<div class="flex items-center justify-between items-end">
<div class="text-xs text-gray-500 flex items-center gap-3">
<span><i class="fa-regular fa-calendar"></i> ${img.lastModified}</span>
${isGridView ? '<span><i class="fa-solid fa-database"></i>' + img.size + ' KB</span>' : ''}
<span><i class="fa-solid fa-ruler-combined"></i> ${img.width}x${img.height}</span>
</div>
<button onclick="downloadImage('${img.url}', '${img.title}')"
class="download-btn flex items-center gap-2 px-3 py-1.5 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 hover:text-green-700 transition-colors text-sm font-medium group/btn">
下载
</button>
</div>
</div>
`;
galleryContainer.appendChild(card);
});
updateViewClass();
}
// 切换视图模式
function switchView() {
isGridView = !isGridView;
if (isGridView) {
viewIcon.className = 'fa-solid fa-list';
viewText.textContent = '列表';
galleryContainer.classList.remove('grid-cols-1');
galleryContainer.classList.add('sm:grid-cols-2', 'md:grid-cols-3', 'lg:grid-cols-3');
} else {
viewIcon.className = 'fa-solid fa-border-all';
viewText.textContent = '网格';
galleryContainer.classList.remove('sm:grid-cols-2', 'md:grid-cols-3', 'lg:grid-cols-3');
galleryContainer.classList.add('grid-cols-1');
}
renderfileList();
showToast(isGridView ? '已切换到网格视图' : '已切换到列表视图', 'info');
}
// 更新视图类名以适配 CSS
function updateViewClass() {
if (!isGridView) {
galleryContainer.classList.add('list-view');
} else {
galleryContainer.classList.remove('list-view');
}
}
// 搜索功能
function handleSearch(e) {
const term = e.target.value.toLowerCase();
fileList = initialfileList.filter(img => img.title.toLowerCase().includes(term));
renderfileList();
}
// 排序功能
function sortfileList(type) {
if (type === 'date') {
if (dateSortType === 'ASC') {
fileList.sort((a, b) => new Date(b.lastModified) - new Date(a.lastModified));
dateSortType = 'DSC';
} else {
fileList.sort((a, b) => new Date(a.lastModified) - new Date(b.lastModified));
dateSortType = 'ASC';
}
} else if (type === 'name') {
if (titleSortType === 'ASC') {
fileList.sort((a, b) => a.title.localeCompare(b.title));
titleSortType = 'DSC';
} else {
fileList.sort((a, b) => b.title.localeCompare(a.title));
titleSortType = 'ASC';
}
}
renderfileList();
showToast(`已按${type === 'date' ? '日期' : '名称'}排序`, 'success');
}
// 核心功能:下载图片
async function downloadImage(url, filename) {
const btn = event.currentTarget;
const originalContent = btn.innerHTML;
// 显示加载状态
btn.disabled = true;
btn.innerHTML = `<div class="loader w-4 h-4 border-2"></div> 下载中...`;
btn.classList.add('opacity-75', 'cursor-not-allowed');
const baseUrl = window.location.href; // 当前页面完整 URL
const relativePath = './mch-banner/banner1.png';
const finalUrl = new URL(url, baseUrl).href;
try {
// 使用 fetch 获取图片 blob,解决跨域和直接打开新窗口的问题
const response = await fetch(finalUrl);
if (!response.ok) throw new Error('网络响应异常');
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
// 创建临时链接触发下载
const link = document.createElement('a');
link.href = blobUrl;
link.download = filename;
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
window.URL.revokeObjectURL(blobUrl);
showToast('下载成功!', 'success');
} catch (error) {
console.error('Download failed:', error);
showToast('下载失败,请重试', 'error');
} finally {
// 恢复按钮状态
btn.disabled = false;
btn.innerHTML = originalContent;
btn.classList.remove('opacity-75', 'cursor-not-allowed');
}
}
// Toast 提示系统
function showToast(message, type = 'info') {
const toast = document.createElement('div');
const colors = {
success: 'bg-green-500',
error: 'bg-red-500',
info: 'bg-blue-500'
};
const icons = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
info: 'fa-info-circle'
};
toast.className = `${colors[type]} text-white px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 transform transition-all duration-300 translate-y-10 opacity-0 min-w-[200px]`;
toast.innerHTML = `
<i class="fa-solid ${icons[type]}"></i>
<span class="font-medium text-sm">${message}</span>
`;
document.getElementById('toastContainer').appendChild(toast);
// 动画进入
requestAnimationFrame(() => {
toast.classList.remove('translate-y-10', 'opacity-0');
});
// 自动移除
setTimeout(() => {
toast.classList.add('translate-y-10', 'opacity-0');
setTimeout(() => {
toast.remove();
}, 300);
}, 3000);
}
// 下载图片并计算 Blob 大小。同时获取像素宽高。
async function getImageDetails(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error('图片加载失败');
// 1. 将响应转换为 Blob
const blob = await response.blob();
// 2. 计算文件大小 (Blob.size 单位是字节)
const sizeInKB = (blob.size / 1024).toFixed(2);
// 3. 获取最后修改日期 (通常远程 Blob 没有此属性,除非从 Header 解析)
// 注意:Blob 对象本身不包含 lastModified,除非你是从 input 文件来的
// 这里我们只能依赖 Header 中的 Last-Modified (如果有的话)
const lastModified = response.headers.get('Last-Modified') || '未知';
// 4. 获取图片宽高 (像素)
return new Promise((resolve) => {
const img = new Image();
img.src = URL.createObjectURL(blob);
img.onload = () => {
resolve({
width: img.naturalWidth,
height: img.naturalHeight,
sizeKB: sizeInKB,
lastModified: lastModified
});
URL.revokeObjectURL(img.src);
};
});
} catch (error) {
console.error('处理图片失败:', error);
return null;
}
}
// 时间戳格式化为 yyyy-MM-dd hh:mm:ss 格式
function formatLastModified(timestamp) {
if (!timestamp) return '未知时间';
const date = new Date(timestamp);
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
// 从 URL 路径中提取文件名
function getFilenameFromUrl(url) {
// 判断是否为相对路径(不以 http://、https://、// 开头)
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('//')) {
// 确保相对路径以 / 开头
if (!url.startsWith('/')) {
url = '/' + url;
}
// 拼接当前页面的基础 URL
url = window.location.origin + url;
}
try {
const urlObj = new URL(url);
const pathname = urlObj.pathname;
const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
return decodeURIComponent(filename) || 'image.jpg';
} catch (e) {
// URL 解析失败,尝试直接从字符串中截取
const parts = url.split('/');
const lastPart = parts[parts.length - 1];
// 去除可能的查询参数
const filename = lastPart.split('?');
return decodeURIComponent(filename) || 'image.jpg';
}
}
</script>
</body>
</html>

970

被折叠的 条评论
为什么被折叠?



