如何压缩 Base64 图片(为什么 Gzip 通常行不通)
了解如何通过优化解码后的图片来减小 Base64,为什么 Gzip 不能直接作为 Data URL,以及怎样在浏览器 JavaScript 中压到 100 KB。
为什么 Base64 通常大约膨胀 33.3%
Base64 是二进制到文本的编码,不是压缩。RFC 4648 把 3 个字节映射为 4 个字符,因此 n 字节的标准带 padding Base64 长度精确为 4 × ceil(n / 3)。约 33.3% 指长输入的比例,还不包含 Data URL 前缀、HTML 引号或 JSON 转义。
三条处理链有什么区别
普通链路是图片字节 → Base64:兼容,但文本更大。Base64 → Gzip → Base64 会增加压缩容器和第二层文本编码,使用方必须逐层还原。Base64 → 解码图片 → 优化像素或格式 → Base64 仍是标准图片数据,适合需要 img src 兼容的场景。
为什么重新文本化后 Gzip 收益通常消失
Gzip 能利用 Base64 文本里的部分规律,所以中间二进制看上去可能更小;但再转成文本又会增加 Base64 开销和 Gzip 头尾。更关键的是,结果是 Gzip 数据,不是 PNG、JPEG 或 WebP,不能不解压就直接放进 img src。
图片格式本身已经做过压缩
JPEG 和有损 WebP 已经紧凑编码视觉信息,PNG 和无损 WebP 也使用格式专用压缩。通用压缩器通常找不到多少新冗余。真正明显的收益多来自减少像素、降低有损质量、去掉元数据,或换成更合适的格式。
在 JavaScript 中正确压缩 Base64 图片
浏览器原生实现应严格校验 Base64、检查真实文件签名、创建 ImageBitmap、绘制到受限 Canvas,再调用 toBlob 或 OffscreenCanvas.convertToBlob,最后对新图片字节进行 Base64 编码。下方示例展示主流程;生产代码还应限制字节和像素、拒绝动画、检测 WebP 并防止旧任务覆盖新结果。
一个简洁的浏览器原生示例
async function compressBase64Image(dataUrl, maxWidth = 1600, quality = 0.82) {
const [header, payload] = dataUrl.split(',', 2);
const mime = header.match(/^data:([^;]+);base64$/)?.[1];
if (!mime || !/^image\/(png|jpeg|webp)$/.test(mime)) throw new Error('Unsupported image');
const binary = atob(payload.replace(/\s+/g, ''));
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
const bitmap = await createImageBitmap(new Blob([bytes], { type: mime }));
try {
const scale = Math.min(1, maxWidth / bitmap.width);
const canvas = document.createElement('canvas');
canvas.width = Math.max(1, Math.round(bitmap.width * scale));
canvas.height = Math.max(1, Math.round(bitmap.height * scale));
const context = canvas.getContext('2d');
if (!context) throw new Error('Canvas 2D unavailable');
context.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/webp', quality));
if (!blob) throw new Error('Encoding failed');
const output = new Uint8Array(await blob.arrayBuffer());
let outputBinary = '';
for (let offset = 0; offset < output.length; offset += 0x8000) {
outputBinary += String.fromCharCode(...output.subarray(offset, offset + 0x8000));
}
return `data:${blob.type};base64,${btoa(outputBinary)}`;
} finally {
bitmap.close();
}
}
怎样把 Base64 图片压到 100 KB
先把 100 KB 当作 102,400 个图片字节,而不是 Base64 字符。JPEG/WebP 可用有界二分搜索找到目标内最高质量;最低质量仍超标时,用 sqrt(目标字节 / 当前字节) 估算等比缩放,并只进行少量轮次。
质量、尺寸和格式是三种不同手段
质量改变编码器决策但不改变像素尺寸;缩放直接删除像素,通常影响更大。照片常适合 JPEG 或有损 WebP,图形和透明图片可能需要 PNG 或 WebP。Auto 应比较真实结果,而不是假定某个格式永远最小。
PNG 透明通道与 JPEG 背景
JPEG 没有 alpha 通道。没有准备背景就把透明 PNG 画进 JPEG Canvas,可能得到意外颜色。可靠的转换器应自动保留 PNG/WebP,或要求用户明确选择并确认 JPEG 背景色。
GIF、动画 WebP、SVG 和多图片容器
普通 Canvas 只捕获一个渲染帧,静默处理 GIF 或动画 WebP 会破坏动画;ICO 可能包含多张图;SVG 是可能带引用的结构化文本。除非实现清晰标注的专用流程,否则应直接拒绝。
本地处理改善隐私,但不等于安全扫描
纯静态浏览器工具可以避免上传和服务端留存,但不能证明恶意或畸形图片是安全的。扩展、剪贴板历史、设备风险、浏览器解码器漏洞和下游使用方式仍然需要考虑。
什么时候应该放弃 Base64
Base64 适合小图标、测试 fixture、自包含文档和明确要求文本的 API。大图更适合 Blob URL、本身可缓存的独立文件,或通过 Blob、FormData、ArrayBuffer 进行二进制上传。
常见问题
Base64 本身能压缩吗?
能作为通用数据压缩,但结果不再是可直接使用的标准图片 Base64。
压缩后的 Base64 还能直接用于 img src 吗?
只有解码后仍是受支持图片时才可以;Gzip 包装的数据必须先解压。
怎样把 Base64 图片减到 100 KB?
先解码,在固定次数内搜索 JPEG/WebP 质量,必要时再等比缩小尺寸。
会损失画质吗?
可能。缩放和有损质量会改变图片信息;PNG 可以像素无损,但未必变小。
为什么 Base64 大约大 33%?
每 3 个字节对应 4 个字符,即 4 × ceil(n / 3)。
图片会上传到服务器吗?
本站压缩器不会上传,全部处理在浏览器本地进行。
Gzip 应放在 Base64 前还是后?
传输时优先使用正常的 HTTP 响应压缩,不要期望 Gzip 包装的 Base64 可直接作为图片源。