<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">动态模糊 · 图片批量工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #0b0d15;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
padding: 20px;
}
.card {
max-width: 1000px;
width: 100%;
background: rgba(20, 24, 32, 0.8);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border-radius: 48px;
padding: 32px 28px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.04);
transition: box-shadow 0.2s;
}
h1 {
display: flex;
align-items: center;
gap: 12px;
font-weight: 500;
font-size: 1.8rem;
letter-spacing: -0.01em;
color: #eef2ff;
margin-bottom: 24px;
}
h1 span {
background: #2b324a;
padding: 4px 14px;
border-radius: 80px;
font-size: 0.9rem;
font-weight: 400;
color: #b3c2e2;
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 16px 20px;
margin-bottom: 28px;
background: #10131f;
padding: 16px 20px;
border-radius: 60px;
border: 1px solid #2a3147;
}
.btn {
background: #252d44;
border: none;
padding: 10px 20px;
border-radius: 40px;
font-weight: 450;
font-size: 0.95rem;
color: #d6e2ff;
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
border: 1px solid #3e4762;
flex-shrink: 0;
}
.btn-primary {
background: #4a5bcd;
border-color: #6e7ee0;
color: white;
box-shadow: 0 4px 10px rgba(74, 91, 205, 0.25);
}
.btn-primary:hover {
background: #5d6fdf;
transform: scale(1.02);
}
.btn-primary:active {
transform: scale(0.96);
}
.btn-outline {
background: transparent;
border: 1px solid #3f4a6b;
color: #b9c8f0;
}
.btn-outline:hover {
background: #1c233b;
border-color: #6574a8;
}
.btn:active {
transform: scale(0.96);
}
.blur-slider {
display: flex;
align-items: center;
gap: 14px;
flex: 1;
min-width: 160px;
}
.blur-slider label {
color: #b5c5f0;
font-size: 0.9rem;
white-space: nowrap;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
background: #262f4a;
height: 6px;
border-radius: 6px;
flex: 1;
min-width: 80px;
outline: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
border-radius: 20px;
background: #7f92f0;
border: 2px solid #b6c6ff;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: 0.1s;
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.12);
background: #95a6ff;
}
.blur-value {
min-width: 44px;
text-align: center;
font-weight: 500;
color: #dce6ff;
background: #1b2136;
padding: 4px 8px;
border-radius: 40px;
font-size: 0.9rem;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
margin: 20px 0 10px;
}
.image-card {
background: #141b2b;
border-radius: 28px;
overflow: hidden;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.5);
transition: transform 0.2s, box-shadow 0.3s;
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #2b344f;
position: relative;
}
.image-card img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: filter 0.2s ease;
will-change: filter;
}
.image-card .remove-btn {
position: absolute;
top: 10px;
right: 10px;
background: #0f1322cc;
backdrop-filter: blur(8px);
border: none;
width: 32px;
height: 32px;
border-radius: 40px;
color: #f0f4ff;
font-size: 20px;
line-height: 26px;
text-align: center;
cursor: pointer;
transition: 0.15s;
opacity: 0;
border: 1px solid #ffffff30;
display: flex;
align-items: center;
justify-content: center;
}
.image-card:hover .remove-btn {
opacity: 1;
}
.image-card .remove-btn:hover {
background: #d2435ccc;
transform: scale(1.05);
}
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 64px 20px;
color: #8596c9;
background: #101624;
border-radius: 40px;
border: 1px dashed #2f3c62;
font-size: 1rem;
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
}
.empty-state svg {
opacity: 0.6;
margin-bottom: 8px;
}
.footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 18px;
color: #6b7db0;
font-size: 0.85rem;
flex-wrap: wrap;
gap: 8px;
}
.footer .count {
background: #1b233b;
padding: 6px 16px;
border-radius: 40px;
border: 1px solid #2c365a;
}
.clear-btn {
background: transparent;
border: none;
color: #8899d4;
text-decoration: underline dotted #3c4a78;
padding: 6px 12px;
cursor: pointer;
border-radius: 30px;
transition: 0.15s;
}
.clear-btn:hover {
color: #bbc8ff;
background: #181f38;
}
@media (max-width: 600px) {
.card { padding: 20px 16px; }
.toolbar { flex-wrap: wrap; padding: 14px; border-radius: 40px; }
.btn { padding: 8px 16px; font-size: 0.85rem; }
.gallery { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
h1 { font-size: 1.4rem; }
}
</style>
</head>
<body>
<div class="card">
<h1>
⚡ 模糊工坊
<span>动态效果</span>
</h1>
<div class="toolbar">
<button class="btn btn-primary" id="addImageBtn">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12v3a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h3"/><path d="M15 6h6v6"/><path d="M10 14 21 3"/></svg>
添加图片
</button>
<div class="blur-slider">
<label>🌫️ 模糊</label>
<input type="range" id="blurRange" min="0" max="20" step="0.2" value="4">
<span class="blur-value" id="blurDisplay">4.0</span>
</div>
<button class="btn-outline btn" id="resetBlurBtn">重置</button>
</div>
<div class="gallery" id="galleryContainer">
<div class="empty-state" id="emptyPlaceholder">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#6574a8" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
<span>点击「添加图片」导入照片</span>
<span style="font-size: 0.8rem; opacity: 0.7;">支持 JPG / PNG / WebP</span>
</div>
</div>
<div class="footer">
<span class="count" id="imageCount">0 张图片</span>
<div>
<button class="clear-btn" id="clearAllBtn">清空全部</button>
</div>
</div>
</div>
<script>
(function(){
// DOM 元素
const gallery = document.getElementById('galleryContainer');
const emptyState = document.getElementById('emptyPlaceholder');
const addBtn = document.getElementById('addImageBtn');
const blurRange = document.getElementById('blurRange');
const blurDisplay = document.getElementById('blurDisplay');
const resetBlurBtn = document.getElementById('resetBlurBtn');
const clearAllBtn = document.getElementById('clearAllBtn');
const imageCount = document.getElementById('imageCount');
// 状态管理
let imageList = []; // 存储图片数据: { id, src (dataUrl) }
let currentBlur = 4; // 默认模糊值
let idCounter = 0;
// 更新模糊值显示
function updateBlurValue(val) {
const v = parseFloat(val);
blurDisplay.textContent = v.toFixed(1);
currentBlur = v;
}
// 应用模糊到所有图片
function applyBlurToAll() {
const imgs = gallery.querySelectorAll('.image-card img');
const blurValue = currentBlur;
imgs.forEach(img => {
img.style.filter = `blur(${blurValue}px)`;
});
}
// 渲染画廊 (基于 imageList)
function renderGallery() {
// 清空 gallery 但保留 empty-placeholder 引用
const items = gallery.querySelectorAll('.image-card');
items.forEach(el => el.remove());
if (imageList.length === 0) {
emptyState.style.display = 'flex';
imageCount.textContent = `0 张图片`;
return;
}
emptyState.style.display = 'none';
// 重新填充卡片
imageList.forEach(item => {
const card = document.createElement('div');
card.className = 'image-card';
card.dataset.id = item.id;
const img = document.createElement('img');
img.src = item.src;
img.alt = '模糊图片';
img.draggable = false;
// 应用当前模糊
img.style.filter = `blur(${currentBlur}px)`;
img.style.transition = 'filter 0.15s ease';
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
removeBtn.innerHTML = '✕';
removeBtn.setAttribute('aria-label', '移除图片');
removeBtn.addEventListener('click', function(e) {
e.stopPropagation();
const id = parseInt(card.dataset.id);
removeImageById(id);
});
card.appendChild(img);
card.appendChild(removeBtn);
gallery.appendChild(card);
});
imageCount.textContent = `${imageList.length} 张图片`;
}
// 根据 id 移除图片
function removeImageById(id) {
imageList = imageList.filter(item => item.id !== id);
renderGallery();
}
// 清空全部图片
function clearAllImages() {
if (imageList.length === 0) return;
imageList = [];
renderGallery();
}
// 添加新图片 (从 File 对象)
function addImageFromFile(file) {
if (!file || !file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = function(e) {
const src = e.target.result;
const newItem = {
id: ++idCounter,
src: src
};
imageList.push(newItem);
renderGallery();
};
reader.readAsDataURL(file);
}
// 打开文件选择器
function openFilePicker() {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.multiple = true;
input.style.display = 'none';
document.body.appendChild(input);
input.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
if (files.length === 0) {
document.body.removeChild(input);
return;
}
files.forEach(file => addImageFromFile(file));
document.body.removeChild(input);
});
input.click();
}
// ------ 事件绑定 ------
// 添加图片按钮
addBtn.addEventListener('click', openFilePicker);
// 模糊滑块
blurRange.addEventListener('input', function(e) {
const val = e.target.value;
updateBlurValue(val);
applyBlurToAll();
});
// 重置模糊 (设为0)
resetBlurBtn.addEventListener('click', function() {
blurRange.value = 0;
updateBlurValue(0);
applyBlurToAll();
});
// 清空按钮
clearAllBtn.addEventListener('click', clearAllImages);
// 额外: 点击空状态触发添加 (方便)
emptyState.addEventListener('click', openFilePicker);
// 初始化模糊显示
blurRange.value = currentBlur;
updateBlurValue(currentBlur);
// 初始无图片,无需渲染
renderGallery();
// 演示:内置两张示例图片(动态效果展示)
function loadDemoImages() {
// 第一个示例 (SVG 矢量风格抽象图 1)
const svg1 = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Crect width='200' height='200' fill='%231b253a'/%3E%3Ccircle cx='100' cy='90' r='60' fill='%233f5ec9' opacity='0.9'/%3E%3Ccircle cx='70' cy='70' r='30' fill='%237992f0' opacity='0.7'/%3E%3Cpolygon points='100,140 140,100 60,100' fill='%23ffb347' opacity='0.7'/%3E%3Ctext x='80' y='170' font-size='22' fill='%23bcccf0' font-weight='500'%3E🌆%3C/text%3E%3C/svg%3E`;
const svg2 = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Crect width='200' height='200' fill='%23202b42'/%3E%3Crect x='30' y='30' width='140' height='140' rx='40' fill='%23344b92' opacity='0.8'/%3E%3Ccircle cx='110' cy='80' r='40' fill='%23f9d976' opacity='0.6'/%3E%3Cpath d='M40 170 L80 110 L120 140 L170 70' stroke='%237aa2ff' stroke-width='6' fill='none' stroke-linecap='round' /%3E%3Ctext x='60' y='180' font-size='24' fill='%23b0c8ff' %3E✨%3C/text%3E%3C/svg%3E`;
// 以文件添加方式模拟 (以便展示移除效果)
// 但直接用 item 推入更简洁
const demoItem1 = { id: ++idCounter, src: svg1 };
const demoItem2 = { id: ++idCounter, src: svg2 };
imageList.push(demoItem1, demoItem2);
renderGallery();
}
// 启动演示 (若用户无图片则显示示例)
// 但我们可以考虑只在画廊为空时加载演示,并延迟片刻让用户看到空状态也可。
setTimeout(() => {
if (imageList.length === 0) {
loadDemoImages();
}
}, 300);
})();
</script>
</body>
</html>
图片模糊工具使用说明
您可以通过简单的操作,为多张图片添加统一的动态模糊效果。

- 图片管理:点击 “添加图片” 按钮,可从本地选择多张图片进行批量导入,每张图片右上角都有一个 按钮,方便您单独移除。
- 模糊调节:通过拖动 “模糊”滑块,您可以实时调整所有图片的模糊程度,数值范围从0到20,点击 “重置” 按钮可将模糊值快速归零。
- 状态反馈:页面底部会显示当前图片总数,当没有图片时,会显示引导提示,点击该区域也可触发图片添加。
标签: 批量处理
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。