<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">✨ 霓虹批量发光工具</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
background: #0b0b1a;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.card {
max-width: 1200px;
width: 100%;
background: #14142b;
border-radius: 40px;
box-shadow: 0 20px 40px rgba(0,0,0,0.8), 0 0 0 2px #3b3b7a inset;
padding: 30px 30px 40px;
transition: box-shadow 0.2s;
}
h1 {
font-size: 2.4rem;
font-weight: 600;
letter-spacing: 2px;
color: #f0e9ff;
text-shadow: 0 0 10px #b47cff, 0 0 20px #7a4dff;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 12px;
}
h1 small {
font-size: 1rem;
font-weight: 400;
background: #2f2f5e;
padding: 6px 14px;
border-radius: 40px;
color: #cfc3ff;
text-shadow: none;
box-shadow: 0 0 6px #aa8cff;
}
.control-panel {
background: #1d1d3e;
padding: 20px 24px;
border-radius: 60px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 18px 14px;
margin: 10px 0 24px 0;
border: 1px solid #3c3c7a;
box-shadow: 0 6px 0 #0a0a18;
}
.file-area {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
flex: 2 1 260px;
}
.file-label {
background: #2d2d66;
padding: 10px 22px;
border-radius: 80px;
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
border: 1px solid #6a6ac0;
box-shadow: 0 4px 0 #15152e;
display: inline-flex;
align-items: center;
gap: 8px;
}
.file-label:hover {
background: #4242a0;
transform: translateY(-2px);
box-shadow: 0 6px 0 #15152e;
}
.file-label:active {
transform: translateY(4px);
box-shadow: 0 1px 0 #15152e;
}
#fileInput {
display: none;
}
.badge-count {
background: #0f0f2a;
padding: 5px 16px;
border-radius: 40px;
color: #b7acff;
font-size: 0.95rem;
border: 1px solid #5c5caa;
}
.slider-group {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px 18px;
background: #12122e;
padding: 8px 18px 8px 18px;
border-radius: 80px;
border: 1px solid #3b3b7a;
}
.slider-group label {
color: #cfc3ff;
letter-spacing: 0.3px;
display: flex;
align-items: center;
gap: 8px;
}
input[type="range"] {
width: 130px;
height: 6px;
background: #2b2b66;
border-radius: 10px;
-webkit-appearance: none;
appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
background: #b38eff;
border-radius: 50%;
border: 2px solid #f0e9ff;
box-shadow: 0 0 12px #7f5eff;
cursor: pointer;
}
.value-badge {
min-width: 32px;
background: #20204a;
padding: 2px 6px;
border-radius: 30px;
text-align: center;
color: #ddd3ff;
font-weight: 500;
}
.action-btn {
background: #3b2a7a;
border: none;
color: white;
padding: 10px 28px;
border-radius: 60px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: 0.1s ease;
border: 1px solid #9a7eff;
box-shadow: 0 6px 0 #1c1a3a;
display: inline-flex;
align-items: center;
gap: 6px;
letter-spacing: 0.5px;
}
.action-btn:active {
transform: translateY(4px);
box-shadow: 0 2px 0 #1c1a3a;
}
.action-btn.primary {
background: #4f2fc7;
border-color: #c3aeff;
}
.action-btn.primary:hover {
background: #6a47e6;
}
.action-btn.secondary {
background: #2e2e5a;
border-color: #7474b0;
}
.action-btn.secondary:hover {
background: #494984;
}
.action-btn:disabled {
opacity: 0.5;
transform: translateY(2px);
box-shadow: 0 2px 0 #1c1a3a;
pointer-events: none;
}
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 22px 16px;
margin: 28px 0 18px;
padding-top: 6px;
}
.image-card {
background: #111128;
border-radius: 24px;
overflow: hidden;
box-shadow: 0 10px 20px rgba(0,0,0,0.6);
transition: transform 0.15s, box-shadow 0.2s;
border: 1px solid #393971;
aspect-ratio: 1 / 1;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.image-card img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: filter 0.2s;
image-rendering: auto;
}
.image-card .remove-btn {
position: absolute;
top: 6px;
right: 6px;
background: #1f1f4fcc;
backdrop-filter: blur(4px);
border: none;
color: #ffb6b6;
font-size: 20px;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: 0.1s;
border: 1px solid #ff8c8c;
box-shadow: 0 0 10px #ff3b3b55;
}
.image-card .remove-btn:hover {
background: #cc3f3fdd;
color: white;
transform: scale(1.05);
}
.empty-message {
grid-column: 1 / -1;
text-align: center;
padding: 40px 20px;
color: #7f7fab;
background: #0e0e28;
border-radius: 40px;
border: 1px dashed #48488a;
font-size: 1.1rem;
}
.footer-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
margin-top: 16px;
gap: 14px;
}
.download-all-btn {
background: #1d9e5c;
border: 1px solid #7dffb0;
box-shadow: 0 6px 0 #0e3d25;
}
.download-all-btn:hover {
background: #28c573;
}
.note {
color: #a89fd6;
font-size: 0.85rem;
border-left: 3px solid #6d5fc7;
padding-left: 14px;
}
@media (max-width: 680px) {
.card { padding: 18px; }
h1 { font-size: 1.9rem; }
.control-panel { border-radius: 32px; padding: 16px; }
.slider-group { flex-wrap: wrap; padding: 6px 14px; }
input[type="range"] { width: 110px; }
}
</style>
</head>
<body>
<div class="card">
<h1>
🌟 霓虹光效
<small>批量·发光引擎</small>
</h1>
<div class="control-panel">
<div class="file-area">
<label for="fileInput" class="file-label">📁 选择图片</label>
<span class="badge-count" id="imageCount">0 张</span>
<span style="color:#8f86c9; font-size:0.9rem;">拖拽或点击</span>
</div>
<div class="slider-group">
<label>💡 强度 <span class="value-badge" id="intensityValue">8</span></label>
<input type="range" id="intensitySlider" min="1" max="25" value="8" step="0.5">
</div>
<button class="action-btn secondary" id="clearBtn">🗑️ 清空</button>
</div>
<!-- 图片网格 -->
<div class="gallery" id="galleryContainer">
<div class="empty-message">✨ 上传图片,霓虹觉醒 ✨</div>
</div>
<div class="footer-actions">
<button class="action-btn primary" id="downloadAllBtn" disabled>⬇️ 下载全部 (霓虹效果)</button>
<span class="note">⚡ 批量应用发光 · 点击图片可单独下载</span>
</div>
</div>
<script>
(function(){
// ---------- 状态 ----------
const imageFiles = []; // 存储原始 File 对象
let currentIntensity = 8; // 默认
// DOM 引用
const fileInput = document.getElementById('fileInput');
const gallery = document.getElementById('galleryContainer');
const imageCountSpan = document.getElementById('imageCount');
const intensitySlider = document.getElementById('intensitySlider');
const intensityValue = document.getElementById('intensityValue');
const clearBtn = document.getElementById('clearBtn');
const downloadAllBtn = document.getElementById('downloadAllBtn');
// ---------- 核心:渲染所有图片 (带霓虹) ----------
function renderGallery() {
// 清空网格,保留占位逻辑
gallery.innerHTML = '';
if (imageFiles.length === 0) {
gallery.innerHTML = `<div class="empty-message">✨ 上传图片,霓虹觉醒 ✨</div>`;
imageCountSpan.textContent = `0 张`;
downloadAllBtn.disabled = true;
return;
}
imageCountSpan.textContent = `${imageFiles.length} 张`;
downloadAllBtn.disabled = false;
// 遍历 File 生成卡片
imageFiles.forEach((file, index) => {
const card = document.createElement('div');
card.className = 'image-card';
card.dataset.index = index;
const img = document.createElement('img');
// 用 objectURL 显示原图 (霓虹效果通过 CSS filter 实现)
const url = URL.createObjectURL(file);
img.src = url;
img.alt = file.name || `图片${index+1}`;
// 应用霓虹发光滤镜 —— 核心效果
applyNeonFilter(img, currentIntensity);
// 删除按钮
const removeBtn = document.createElement('button');
removeBtn.className = 'remove-btn';
removeBtn.innerHTML = '✕';
removeBtn.setAttribute('aria-label', '移除图片');
removeBtn.addEventListener('click', (e) => {
e.stopPropagation();
removeImage(index);
});
// 点击卡片下载单张 (霓虹效果)
card.addEventListener('click', (e) => {
// 如果点到删除按钮不触发下载
if (e.target.closest('.remove-btn')) return;
downloadSingleImage(index);
});
card.style.cursor = 'pointer';
card.appendChild(img);
card.appendChild(removeBtn);
gallery.appendChild(card);
});
// 释放之前的objectURL?为了防止内存堆积,但每次渲染重新生成,之前的URL会丢失,保留轻量策略
// 更好:在render前释放旧URL,但难以追踪,我们使用一次性URL+自动回收,注意:当移除图片时需手动revoke
// 简单处理:每次渲染后旧的objectURL没有引用会被GC,但可能延迟,我们保存URL数组以便清理?不用太担心。
// 采用在 remove 时 revoke
}
// ---------- 霓虹滤镜 ----------
function applyNeonFilter(imgElement, intensity) {
// 强度映射:1~25 → 模糊 1~18px,亮度微调,对比度略增强,并增加彩色发光感
const blur = Math.min(18, Math.max(1, intensity * 0.75));
const brightness = 1 + intensity * 0.02; // 1.0 ~ 1.5
const contrast = 1 + intensity * 0.015; // 1.0 ~ 1.375
const saturate = 1.2 + intensity * 0.04; // 1.2 ~ 2.2
// 关键:drop-shadow 模拟霓虹光晕 (多色阴影叠加)
// 用多层阴影制造彩色霓虹感: 洋红/青/黄
const shadowColor1 = `rgba(255, 70, 230, ${0.4 + intensity * 0.02})`;
const shadowColor2 = `rgba(70, 200, 255, ${0.3 + intensity * 0.015})`;
const shadowColor3 = `rgba(255, 220, 50, ${0.15 + intensity * 0.01})`;
const shadowSize = 8 + intensity * 0.9;
// 使用 filter 属性融合
img.style.filter = `
brightness(${brightness})
contrast(${contrast})
saturate(${saturate})
drop-shadow(0 0 ${shadowSize}px ${shadowColor1})
drop-shadow(0 0 ${shadowSize * 0.7}px ${shadowColor2})
drop-shadow(0 0 ${shadowSize * 0.4}px ${shadowColor3})
`;
}
// ---------- 更新所有卡片霓虹 (滑块调整) ----------
function updateAllNeonFilters() {
const images = gallery.querySelectorAll('.image-card img');
images.forEach(img => {
applyNeonFilter(img, currentIntensity);
});
}
// ---------- 移除图片 ----------
function removeImage(index) {
if (index < 0 || index >= imageFiles.length) return;
// 如果有 objectURL 需要释放 (虽然浏览器会GC,但主动释放更好)
const cards = gallery.querySelectorAll('.image-card');
if (cards[index]) {
const img = cards[index].querySelector('img');
if (img && img.src && img.src.startsWith('blob:')) {
URL.revokeObjectURL(img.src);
}
}
imageFiles.splice(index, 1);
renderGallery(); // 重新渲染
}
// ---------- 清空全部 ----------
function clearAllImages() {
// 释放所有 blob url
const images = gallery.querySelectorAll('img');
images.forEach(img => {
if (img.src && img.src.startsWith('blob:')) {
URL.revokeObjectURL(img.src);
}
});
imageFiles.length = 0;
renderGallery();
}
// ---------- 添加文件 ----------
function addFiles(files) {
let added = 0;
for (const file of files) {
if (file.type && file.type.startsWith('image/')) {
imageFiles.push(file);
added++;
}
}
if (added > 0) {
renderGallery();
} else {
alert('未检测到有效图片文件 (jpg, png, webp, gif 等)');
}
}
// ---------- 下载单张 (霓虹效果) ----------
function downloadSingleImage(index) {
if (index < 0 || index >= imageFiles.length) return;
const file = imageFiles[index];
// 利用 Canvas 绘制原图 + 应用同款霓虹滤镜 (截取)
const img = new Image();
const objectURL = URL.createObjectURL(file);
img.onload = function() {
// 创建 canvas
const canvas = document.createElement('canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
const ctx = canvas.getContext('2d');
// 绘制原图
ctx.drawImage(img, 0, 0);
// 应用 CSS 滤镜模拟? 但 canvas 不支持 drop-shadow 直接绘制。
// 但为了保持霓虹效果,我们采用裁剪 + 全局复合 + 模拟简单发光 (drawImage + 阴影api)
// 更靠谱:用 canvas filter 属性 (支持 drop-shadow 和 其他)
ctx.filter = `
brightness(${1 + currentIntensity * 0.02})
contrast(${1 + currentIntensity * 0.015})
saturate(${1.2 + currentIntensity * 0.04})
drop-shadow(0 0 ${8 + currentIntensity * 0.9}px rgba(255, 70, 230, ${0.4 + currentIntensity * 0.02}))
drop-shadow(0 0 ${(8 + currentIntensity * 0.9) * 0.7}px rgba(70, 200, 255, ${0.3 + currentIntensity * 0.015}))
drop-shadow(0 0 ${(8 + currentIntensity * 0.9) * 0.4}px rgba(255, 220, 50, ${0.15 + currentIntensity * 0.01}))
`;
// 重新绘制原图以应用滤镜 (在同一个canvas上)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0);
// 导出
canvas.toBlob((blob) => {
if (!blob) return;
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
const ext = file.name.split('.').pop() || 'png';
link.download = `neon_${file.name.replace(/\.[^.]+$/, '')}.${ext}`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
// 释放 img 的 objectURL
URL.revokeObjectURL(objectURL);
}, file.type || 'image/png');
};
img.onerror = function() {
URL.revokeObjectURL(objectURL);
alert('图片加载失败,无法下载');
};
img.src = objectURL;
}
// ---------- 下载全部 (霓虹效果) ----------
function downloadAllImages() {
if (imageFiles.length === 0) return;
// 逐个下载,简单触发,为避免浏览器拦截,使用间隔
imageFiles.forEach((_, idx) => {
setTimeout(() => {
downloadSingleImage(idx);
}, idx * 300); // 延时防止弹窗拦截
});
}
// ---------- 事件绑定 ----------
// 文件选择
fileInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
if (files.length) {
addFiles(files);
}
this.value = ''; // 允许重复选择相同文件
});
// 拖拽上传 (全局拖放)
let dragCounter = 0;
const cardElement = document.querySelector('.card');
cardElement.addEventListener('dragenter', (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter++;
cardElement.style.boxShadow = '0 0 0 3px #b38eff, 0 20px 40px rgba(0,0,0,0.8)';
});
cardElement.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
});
cardElement.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter--;
if (dragCounter === 0) {
cardElement.style.boxShadow = '';
}
});
cardElement.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
dragCounter = 0;
cardElement.style.boxShadow = '';
const files = Array.from(e.dataTransfer.files);
if (files.length) {
addFiles(files);
}
});
// 滑块调整
intensitySlider.addEventListener('input', function() {
currentIntensity = parseFloat(this.value);
intensityValue.textContent = currentIntensity;
// 更新现有卡片
updateAllNeonFilters();
});
// 清空
clearBtn.addEventListener('click', clearAllImages);
// 下载全部
downloadAllBtn.addEventListener('click', downloadAllImages);
// 如果点击空白区域或误触,额外保护?不需要
// 初始化
renderGallery();
intensityValue.textContent = currentIntensity;
// 全局暴露(可选)
})();
</script>
</body>
</html>
霓虹光效与批量操作
您可以通过简单的步骤,为多张图片快速添加统一的霓虹发光效果。

- 批量处理与实时预览:您可以一次性上传多张图片,所有图片会以网格形式展示,通过调整“强度”滑块,所有图片的霓虹发光效果会实时同步变化,方便您找到最佳效果。
- 灵活的下载方式:每张图片卡片都支持点击单独下载其霓虹效果版本,您也可以使用底部的“下载全部”按钮,一次性保存所有处理后的图片。
- 直观的图片管理:每张图片右上角都配有移除按钮,方便您从批次中剔除不需要的图片。“清空”按钮则可以一键重置整个图片列表。
标签: 批量处理
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。