<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">📸 相册生成器 · 图片批量分享</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Segoe UI', Roboto, system-ui, -apple-system, sans-serif;
background: linear-gradient(145deg, #f6f9fc 0%, #e9f0f5 100%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 1.5rem 1rem;
}
.app-card {
max-width: 1100px;
width: 100%;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 48px;
padding: 2rem 1.8rem 2.5rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25), 0 0 0 1px rgba(255, 255, 255, 0.6);
transition: all 0.2s ease;
}
h1 {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
font-size: 2.2rem;
letter-spacing: -0.02em;
color: #1e293b;
margin-bottom: 0.2rem;
}
.subhead {
display: flex;
justify-content: space-between;
align-items: flex-start;
flex-wrap: wrap;
margin-bottom: 1.2rem;
border-bottom: 2px solid rgba(148, 163, 184, 0.3);
padding-bottom: 0.8rem;
}
.subhead p {
color: #475569;
font-weight: 450;
font-size: 1rem;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.8rem 1rem;
align-items: center;
margin: 1rem 0 1.8rem 0;
}
.btn {
background: white;
border: none;
padding: 0.65rem 1.6rem;
border-radius: 60px;
font-weight: 500;
font-size: 0.95rem;
display: inline-flex;
align-items: center;
gap: 0.4rem;
box-shadow: 0 2px 6px rgba(0, 20, 30, 0.08);
transition: 0.2s;
cursor: pointer;
color: #0f172a;
border: 1px solid rgba(148, 163, 184, 0.25);
background: rgba(255,255,255,0.75);
backdrop-filter: blur(4px);
}
.btn-primary {
background: #1e293b;
border: 1px solid #1e293b;
color: white;
box-shadow: 0 6px 14px rgba(30, 41, 59, 0.25);
}
.btn-primary:hover {
background: #0f172a;
transform: scale(0.97);
}
.btn-outline {
background: transparent;
border: 1px solid #94a3b8;
}
.btn:hover:not(:disabled) {
background: #f1f5f9;
transform: translateY(-1px);
}
.btn-primary:hover:not(:disabled) {
background: #0f172a;
}
.btn:disabled {
opacity: 0.5;
cursor: default;
transform: none;
}
.badge {
background: #dbeafe;
color: #1e40af;
padding: 0.2rem 0.9rem;
border-radius: 40px;
font-size: 0.85rem;
font-weight: 500;
margin-left: 0.3rem;
}
.upload-area {
background: rgba(255, 255, 255, 0.5);
border: 2px dashed #94a3b8;
border-radius: 30px;
padding: 1.8rem 1.5rem;
text-align: center;
margin: 0.5rem 0 1.8rem 0;
transition: 0.2s;
cursor: pointer;
}
.upload-area:hover {
background: rgba(255, 255, 255, 0.8);
border-color: #3b82f6;
}
.upload-area svg {
width: 42px;
height: 42px;
color: #64748b;
margin-bottom: 0.2rem;
}
.upload-area p {
color: #334155;
font-weight: 450;
}
.upload-area input[type="file"] {
display: none;
}
.gallery-container {
background: rgba(255, 255, 255, 0.3);
border-radius: 32px;
padding: 1rem 1rem 0.2rem 1rem;
backdrop-filter: blur(2px);
margin: 1rem 0 1.2rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.8rem;
margin-bottom: 0.5rem;
}
.grid-item {
position: relative;
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0,0,0,0.04);
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid rgba(255, 255, 255, 0.6);
transition: 0.1s ease;
}
.grid-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
background: #f1f5f9;
}
.grid-item .remove-btn {
position: absolute;
top: 6px;
right: 6px;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
color: white;
border: none;
border-radius: 40px;
width: 26px;
height: 26px;
font-size: 16px;
line-height: 1;
cursor: pointer;
transition: 0.15s;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.7;
}
.grid-item .remove-btn:hover {
opacity: 1;
background: rgba(180, 35, 35, 0.85);
transform: scale(1.05);
}
.grid-empty {
grid-column: 1 / -1;
text-align: center;
padding: 2.5rem 1rem;
color: #64748b;
background: rgba(255,255,255,0.2);
border-radius: 40px;
font-size: 1rem;
}
.action-bar {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 1rem 0.8rem;
margin-top: 1.2rem;
}
.share-card {
background: rgba(255, 255, 255, 0.4);
backdrop-filter: blur(6px);
padding: 1rem 1.5rem;
border-radius: 60px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.8rem 1.2rem;
box-shadow: 0 2px 12px rgba(0,0,0,0.02);
border: 1px solid rgba(255,255,255,0.7);
}
.share-card span {
color: #1e293b;
font-weight: 500;
}
.share-link-box {
background: white;
border-radius: 60px;
padding: 0.4rem 0.4rem 0.4rem 1.5rem;
display: flex;
align-items: center;
flex: 1;
min-width: 180px;
border: 1px solid #d1d9e8;
}
.share-link-box input {
border: none;
background: transparent;
padding: 0.4rem 0;
flex: 1;
font-size: 0.9rem;
color: #0f172a;
outline: none;
width: 100px;
}
.share-link-box button {
background: #1e293b;
border: none;
color: white;
padding: 0.4rem 1.2rem;
border-radius: 40px;
font-weight: 500;
font-size: 0.85rem;
cursor: pointer;
transition: 0.15s;
white-space: nowrap;
}
.share-link-box button:hover {
background: #0f172a;
}
.footer-note {
margin-top: 1.5rem;
font-size: 0.8rem;
color: #64748b;
text-align: center;
}
@media (max-width: 650px) {
.app-card { padding: 1.2rem; }
h1 { font-size: 1.8rem; }
.grid { grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
.share-card { flex-direction: column; align-items: stretch; }
.share-link-box { min-width: unset; }
}
</style>
</head>
<body>
<div class="app-card">
<div class="subhead">
<h1>🖼️ 相册生成器</h1>
<p><span class="badge" id="imageCountDisplay">0 张</span> 一键分享</p>
</div>
<!-- 上传区域 -->
<div class="upload-area" id="uploadArea">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
<p>拖拽或点击上传图片 <span style="opacity:0.6; margin-left: 6px;">(JPEG, PNG, WEBP)</span></p>
<input type="file" id="fileInput" multiple accept="image/png, image/jpeg, image/webp">
</div>
<!-- 工具栏 -->
<div class="toolbar">
<button class="btn" id="clearAllBtn" disabled>🗑️ 清空相册</button>
<button class="btn btn-outline" id="sortByNameBtn">📛 按名称排序</button>
<button class="btn btn-outline" id="reverseOrderBtn">🔄 反转顺序</button>
</div>
<!-- 图片网格 -->
<div class="gallery-container">
<div class="grid" id="imageGrid">
<div class="grid-empty">✨ 添加图片,构建你的相册</div>
</div>
</div>
<!-- 分享区域 -->
<div class="action-bar">
<div class="share-card">
<span>🔗 分享相册</span>
<div class="share-link-box">
<input type="text" id="shareLinkInput" placeholder="生成分享链接" readonly value="">
<button id="copyShareBtn" disabled>复制</button>
</div>
<button class="btn btn-primary" id="generateShareBtn" disabled>✨ 生成分享</button>
</div>
</div>
<div class="footer-note">
⚡ 图片仅保存在本地浏览器,生成相册链接可分享 (Base64 编码)
</div>
</div>
<script>
(function() {
// ----- 核心状态 -----
let images = []; // 存储 { id, name, dataUrl, sortKey }
let shareLinkGenerated = '';
const grid = document.getElementById('imageGrid');
const fileInput = document.getElementById('fileInput');
const uploadArea = document.getElementById('uploadArea');
const clearAllBtn = document.getElementById('clearAllBtn');
const sortByNameBtn = document.getElementById('sortByNameBtn');
const reverseOrderBtn = document.getElementById('reverseOrderBtn');
const generateShareBtn = document.getElementById('generateShareBtn');
const copyShareBtn = document.getElementById('copyShareBtn');
const shareLinkInput = document.getElementById('shareLinkInput');
const imageCountDisplay = document.getElementById('imageCountDisplay');
// ----- 辅助函数 -----
function updateUI() {
// 更新计数
const count = images.length;
imageCountDisplay.textContent = count + ' 张';
clearAllBtn.disabled = count === 0;
generateShareBtn.disabled = count === 0;
// 如果图片为0,清空分享链接
if (count === 0) {
shareLinkInput.value = '';
shareLinkGenerated = '';
copyShareBtn.disabled = true;
} else {
// 如果已有分享链接,但图片变化,置为过期 (但保留显示, 用户可重新生成)
// 每次图片变动, 令分享链接失效,但不清除显示 (重新生成会覆盖)
// 但更合理: 图片变动时清空分享链接显示
shareLinkInput.value = '';
shareLinkGenerated = '';
copyShareBtn.disabled = true;
}
renderGrid();
}
// 渲染网格
function renderGrid() {
if (images.length === 0) {
grid.innerHTML = `<div class="grid-empty">🖼️ 暂无图片,点击上方添加</div>`;
return;
}
let html = '';
images.forEach((img, index) => {
html += `
<div class="grid-item" data-index="${index}">
<img src="${img.dataUrl}" alt="${img.name || '图片'}">
<button class="remove-btn" data-index="${index}" title="移除图片">✕</button>
</div>
`;
});
grid.innerHTML = html;
// 绑定移除事件 (事件委托也可,但直接绑定更清晰)
document.querySelectorAll('.grid-item .remove-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.stopPropagation();
const idx = parseInt(this.dataset.index, 10);
if (!isNaN(idx) && idx >= 0 && idx < images.length) {
images.splice(idx, 1);
updateUI();
}
});
});
}
// 添加图片 (FileList)
function addImagesFromFileList(fileList) {
if (!fileList || fileList.length === 0) return;
const validTypes = ['image/jpeg', 'image/png', 'image/webp'];
const files = Array.from(fileList).filter(f => validTypes.includes(f.type));
if (files.length === 0) {
alert('请选择 JPEG / PNG / WEBP 格式的图片');
return;
}
let loaded = 0;
files.forEach(file => {
const reader = new FileReader();
reader.onload = function(e) {
const dataUrl = e.target.result;
// 生成简短唯一标识
const id = Date.now() + '-' + Math.random().toString(36).substr(2, 6) + '-' + loaded;
const name = file.name || `image_${Date.now()}`;
images.push({
id,
name,
dataUrl,
sortKey: name.toLowerCase()
});
loaded++;
// 最后一个文件加载完成再更新UI (批量更新)
if (loaded === files.length) {
updateUI();
}
};
reader.onerror = function() {
console.warn('读取失败:', file.name);
loaded++;
if (loaded === files.length) {
updateUI();
}
};
reader.readAsDataURL(file);
});
// 若文件都无效, loaded不会增加,但触发更新由上面逻辑完成?files长度可能为0, 但之前有return
// 如果没有有效文件,loaded不会增加, 但已经在files空时return,没问题。
}
// ----- 排序功能 -----
function sortByName() {
images.sort((a, b) => {
const nameA = a.sortKey || '';
const nameB = b.sortKey || '';
return nameA.localeCompare(nameB);
});
updateUI();
}
function reverseOrder() {
images.reverse();
updateUI();
}
// ----- 生成分享 (将图片数据编码为 JSON + Base64) -----
function generateShare() {
if (images.length === 0) {
shareLinkInput.value = '';
shareLinkGenerated = '';
copyShareBtn.disabled = true;
return;
}
// 提取必要数据 (不存储大base64? 但dataUrl本身就是base64, 需要保留)
const shareData = images.map(img => ({
name: img.name,
data: img.dataUrl
}));
try {
const jsonStr = JSON.stringify(shareData);
const encoded = btoa(encodeURIComponent(jsonStr)); // 安全base64
const shareUrl = `album://${encoded}`; // 自定义协议,但这里仅仅作为分享文本
shareLinkGenerated = shareUrl;
shareLinkInput.value = shareUrl;
copyShareBtn.disabled = false;
} catch (e) {
alert('生成分享链接失败,请减少图片数量或尺寸');
console.error(e);
}
}
// 复制到剪贴板
function copyShareLink() {
if (!shareLinkGenerated || shareLinkInput.value === '') {
copyShareBtn.disabled = true;
return;
}
// 使用现代 clipboard API
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(shareLinkInput.value).then(() => {
alert('✅ 相册链接已复制!');
}).catch(() => {
fallbackCopy();
});
} else {
fallbackCopy();
}
}
function fallbackCopy() {
shareLinkInput.select();
try {
const success = document.execCommand('copy');
if (success) {
alert('✅ 相册链接已复制!');
} else {
alert('⚠️ 复制失败,请手动复制');
}
} catch (e) {
alert('⚠️ 复制失败,请手动复制');
}
}
// ----- 事件绑定 -----
// 1. 上传区域点击
uploadArea.addEventListener('click', function(e) {
if (e.target.tagName !== 'INPUT') {
fileInput.click();
}
});
fileInput.addEventListener('change', function(e) {
if (this.files && this.files.length > 0) {
addImagesFromFileList(this.files);
}
this.value = ''; // 允许重复选择相同文件
});
// 2. 拖拽上传 (整个区域)
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
e.stopPropagation();
this.style.borderColor = '#3b82f6';
this.style.background = 'rgba(59, 130, 246, 0.08)';
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
this.style.borderColor = '#94a3b8';
this.style.background = 'rgba(255,255,255,0.5)';
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
e.stopPropagation();
this.style.borderColor = '#94a3b8';
this.style.background = 'rgba(255,255,255,0.5)';
if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
addImagesFromFileList(e.dataTransfer.files);
}
});
// 3. 清空
clearAllBtn.addEventListener('click', function() {
if (images.length === 0) return;
if (confirm('确定清空所有图片吗?')) {
images = [];
updateUI();
}
});
// 4. 排序
sortByNameBtn.addEventListener('click', sortByName);
reverseOrderBtn.addEventListener('click', reverseOrder);
// 5. 生成分享
generateShareBtn.addEventListener('click', generateShare);
// 6. 复制分享
copyShareBtn.addEventListener('click', copyShareLink);
// 7. 点击分享输入框全选
shareLinkInput.addEventListener('click', function() {
this.select();
});
// 初始化UI
updateUI();
// 额外:演示预置图片 (可选) —— 为了方便测试,添加几张示例 (注释掉,用户自行上传)
// 如果您希望开箱即用示例,可解除注释 (但为保持整洁,不添加)
// 但是我们可以提示用户
console.log('📸 相册生成器已启动,上传图片即可生成分享链接');
})();
</script>
</body>
</html>
相册制作与分享流程
您可以通过上传、管理和分享三个核心步骤,快速创建一个可分享的图片集合。

- 图片管理:您可以通过点击或拖拽的方式上传 JPEG、PNG 或 WEBP 格式的图片,上传后,图片会以网格形式清晰展示,您还可以对单张图片进行移除,或使用“清空相册”、“按名称排序”、“反转顺序”等工具进行整体管理。
- 分享链接生成:在相册内容确定后,点击“✨ 生成分享”按钮,工具会将您相册中的所有图片数据进行编码,并生成一个专属的分享链接,这个链接包含了所有图片信息。
- 链接复制与分享:生成的链接会显示在输入框中,您只需点击“复制”按钮即可将链接分享给他人,此链接基于图片数据生成,图片数量或尺寸较大时可能影响生成效率。
优化建议: 您可以根据需要调整 grid 的 grid-template-columns 属性值(第103行),以更改相册网格的列宽和布局密度。
标签: 相册生成
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播