在线转图形
注意逻辑区分清楚: ✅ 原图带透明(BMP/PNG 有 Alpha),导出 JPG:必须填充底色 ✅ 导出 PNG:PNG 本身支持透明,默认保留透明;如果你想要【把透明区域替换成指定颜色(白色 / 黑色 / 自定义)、不再透明】,这个需求可以做
下面这个新版增加:
- 开启「替换透明」复选框
- 拾色器选填充颜色(默认白色
#ffffff) - 只在原图有透明通道、并且勾选替换时才上色;不勾选则 PNG 保留透明
- 画质滑块对 PNG 无效(浏览器 canvas 的 PNG 是无损)
- 保留原有全部功能:拖拽、多格式互转、打包 ZIP
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>本地图片批量转换(支持透明替换)</title>
<style>
*{box-sizing:border-box;font-family:system-ui, -apple-system, BlinkMacSystemFont, sans-serif;margin:0;padding:0}
body{padding:24px;max-width:920px;margin:0 auto;background:#f6f7f9;color:#222}
h2{margin-bottom:20px;font-weight:600}
.card{background:#fff;border-radius:14px;padding:24px;box-shadow:0 2px 12px rgba(0,0,0,0.06)}
.drop-area{border:2px dashed #cbd5e1;border-radius:10px;padding:40px;text-align:center;transition:0.2s;margin-bottom:20px}
.drop-area.active{border-color:#2563eb;background:#eff6ff}
input[type=file]{display:none}
.ctrl-row{display:flex;flex-wrap:wrap;gap:16px;align-items:center;margin-bottom:16px}
.ctrl-item{display:flex;align-items:center;gap:8px}
label{font-size:14px;color:#444}
select,button,input[type=range],input[type=color]{padding:7px 10px;border-radius:6px;border:1px solid #d1d5db;font-size:14px}
button{background:#2563eb;color:white;border:none;cursor:pointer;transition:0.2s}
button:hover{background:#1d4ed8}
button.secondary{background:#f1f5f9;color:#222}
button.secondary:hover{background:#e2e8f0}
#log{margin-top:16px;padding:12px;background:#f8fafc;border-radius:8px;font-size:13px;min-height:120px;white-space:pre-wrap;overflow:auto;max-height:240px}
</style>
</head>
<body>
<div class="card">
<h2>🖼️ 本地图片批量转换|透明区域自定义底色</h2>
<div class="drop-area" id="dropBox">
<p>拖拽图片到这里 / 点击选择文件</p>
<input type="file" id="fileInput" multiple accept=".bmp,.png,.jpg,.jpeg,.webp">
</div>
<div class="ctrl-row">
<div class="ctrl-item">
<label>输出格式:</label>
<select id="outFormat">
<option value="image/jpeg">JPG</option>
<option value="image/png">PNG</option>
<option value="image/webp">WebP</option>
</select>
</div>
<div class="ctrl-item">
<label>画质:</label>
<input type="range" id="quality" min="0.1" max="1" step="0.05" value="0.9">
<span id="qText">0.9</span>
</div>
<div class="ctrl-item">
<input type="checkbox" id="replaceAlpha">
<label for="replaceAlpha">替换透明区域</label>
<input type="color" id="alphaColor" value="#ffffff">
</div>
<div class="ctrl-item">
<button id="convertBtn">开始转换</button>
<button id="zipBtn" class="secondary">打包ZIP下载</button>
<button id="clearBtn" class="secondary">清空</button>
</div>
</div>
<div id="log"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js"></script>
<script>
const dropBox = document.getElementById('dropBox');
const fileInput = document.getElementById('fileInput');
const convertBtn = document.getElementById('convertBtn');
const zipBtn = document.getElementById('zipBtn');
const clearBtn = document.getElementById('clearBtn');
const logDom = document.getElementById('log');
const qualityInput = document.getElementById('quality');
const qText = document.getElementById('qText');
const outFormatSel = document.getElementById('outFormat');
const replaceAlphaCb = document.getElementById('replaceAlpha');
const alphaColorPicker = document.getElementById('alphaColor');
let fileList = [];
let resultList = [];
qualityInput.oninput = () => qText.innerText = qualityInput.value;
// 拖拽
dropBox.onclick = () => fileInput.click();
dropBox.ondragover = e => { e.preventDefault(); dropBox.classList.add('active'); };
dropBox.ondragleave = () => dropBox.classList.remove('active');
dropBox.ondrop = e => {
e.preventDefault();
dropBox.classList.remove('active');
const fs = Array.from(e.dataTransfer.files).filter(f => {
const ext = f.name.toLowerCase();
return ext.endsWith('.bmp') || ext.endsWith('.png') || ext.endsWith('.jpg') || ext.endsWith('.jpeg') || ext.endsWith('.webp');
});
fileList = fs;
logDom.innerText = `✅ 已选中 ${fs.length} 个文件`;
};
fileInput.onchange = () => {
const fs = Array.from(fileInput.files).filter(f => {
const ext = f.name.toLowerCase();
return ext.endsWith('.bmp') || ext.endsWith('.png') || ext.endsWith('.jpg') || ext.endsWith('.jpeg') || ext.endsWith('.webp');
});
fileList = fs;
logDom.innerText = `✅ 已选中 ${fs.length} 个文件`;
};
// hex 转 rgb
function hexToRgb(hex) {
const r = parseInt(hex.slice(1,3),16);
const g = parseInt(hex.slice(3,5),16);
const b = parseInt(hex.slice(5,7),16);
return {r,g,b};
}
function getExtByMime(mime) {
if (mime === 'image/jpeg') return 'jpg';
if (mime === 'image/png') return 'png';
if (mime === 'image/webp') return 'webp';
return 'jpg';
}
async function convertImage(file, targetMime, quality, fillAlpha, fillColorHex) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
// 如果勾选替换透明,先铺满底色
if(fillAlpha){
const c = hexToRgb(fillColorHex);
ctx.fillStyle = `rgb(${c.r},${c.g},${c.b})`;
ctx.fillRect(0,0,canvas.width,canvas.height);
}
ctx.drawImage(img, 0, 0);
canvas.toBlob(blob => {
if (!blob) return reject('转码失败');
const baseName = file.name.replace(/\.(bmp|png|jpg|jpeg|webp)$/i, '');
const newName = baseName + '.' + getExtByMime(targetMime);
resolve({ name: newName, blob });
}, targetMime, quality);
};
img.onerror = reject;
img.src = e.target.result;
};
reader.readAsDataURL(file);
});
}
convertBtn.onclick = async () => {
if (!fileList.length) { alert('请先选择图片!'); return; }
const targetMime = outFormatSel.value;
const q = Number(qualityInput.value);
const fillAlpha = replaceAlphaCb.checked;
const fillColor = alphaColorPicker.value;
resultList = [];
logDom.innerText = '⏳ 转换中...\n';
for (const f of fileList) {
try {
const res = await convertImage(f, targetMime, q, fillAlpha, fillColor);
resultList.push(res);
logDom.innerText += `✅ ${f.name} → ${res.name}\n`;
} catch (err) {
logDom.innerText += `❌ ${f.name} 失败:${err.message}\n`;
}
}
logDom.innerText += `🎉 转换完成,共 ${resultList.length} 个成功`;
};
zipBtn.onclick = async () => {
if (!resultList.length) { alert('请先执行转换!'); return; }
const zip = new JSZip();
for (const item of resultList) {
zip.file(item.name, item.blob);
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
const a = document.createElement('a');
a.href = URL.createObjectURL(zipBlob);
a.download = '图片转换结果.zip';
a.click();
URL.revokeObjectURL(a.href);
};
clearBtn.onclick = () => {
fileList = [];
resultList = [];
fileInput.value = '';
logDom.innerText = '';
};
</script>
</body>
</html>
使用说明
- 不勾选【替换透明区域】
- PNG 输出:保留原图透明通道 ✅
- JPG/WebP:本身不支持透明,canvas 底层会自动填充黑色(这是浏览器限制,JPG 没有 alpha)
- 勾选【替换透明区域】,右边拾色器选颜色
- 不管输出是 JPG / PNG / WebP,透明区域都会被你选的颜色铺满,图片不再透明
- 比如选白色
#ffffff,就是白底
- 拾色器可以选任意颜色,Mac Safari、Chrome 都正常
举个场景
- 你有一张带透明的 PNG,想导出 PNG、但是透明地方改成白色:勾选 + 选白色,转换
- 你想保留透明 PNG:不勾选,直接导出 PNG
如果你想要,我还可以再做一个:离线独立版(内置 jszip,断网也能用)