图片压缩不是单纯把质量滑块调低。文件体积由像素数量、颜色信息、编码格式和元数据共同决定。LemonArt 的压缩工具以浏览器 Canvas 重新编码为核心,实时反馈压缩前后大小,并在本地完成整个过程,让用户可以在隐私和体积之间做出明确选择。
一、先区分有损压缩与无损压缩
PNG 的无损特性适合界面截图、透明素材和需要逐像素一致的图形,但照片通常会产生较大的文件。JPG 通过丢弃部分高频细节降低体积,WebP 则在照片和带透明度的素材上提供更灵活的编码选择。质量百分比不是跨格式统一的画质标准,只能作为同一编码器内的相对控制。
- 照片、封面和网页缩略图:优先尝试 WebP 或 JPG。
- 透明插画和界面截图:优先保留 PNG 或选择支持 Alpha 的 WebP。
- 需要严格可逆:保留原始格式或使用无损流程。
- 社交平台上传前:先限制像素尺寸,再选择质量,通常比只调质量更有效。
二、像素尺寸往往比质量更影响体积
一张 4000 × 3000 的照片有 1200 万个像素,即使把质量从 90 降到 80,文件仍然可能很大。如果展示区域只需要 1200 像素宽,先按目标尺寸缩放可以显著减少编码工作和下载成本。LemonArt 的图片预处理页面把缩放独立出来,用户可以先精确控制宽高,再回到压缩页面选择输出格式。
三、为什么压缩工具需要显示真实结果
质量值只是输入参数,真正有价值的是编码后的 Blob 大小和实际节省比例。相同图片在不同格式和浏览器实现中可能产生不同体积,因此 LemonArt 在每个文件完成后计算 savedPercent,并保留原始大小、结果大小和下载文件。用户可以直接比较,而不是凭经验猜测。
反馈的粒度也要落到位:每行文件的状态位独立显示 waiting / converting(含实时百分比)/ done / error,转换完成的行立刻出现下载按钮与“节省 xx%”标签;全量完成后,汇总按钮才会出现“全部下载”。这样用户始终知道哪张图成功了、哪张还需要处理,而不是对着一个全局进度条猜。
const savedPercent = Math.max(0, Math.round(
(1 - resultBytes / originalBytes) * 100
))
// 状态位:ready → converting(progress) → done(下载按钮 + savedPercent)
// 失败时保留 error 信息,其余任务继续执行ts四、本地压缩的隐私和性能边界
本地处理意味着图片不会上传到服务器,适合证件、设计稿、客户素材和未发布内容。但浏览器仍受设备内存限制,大图批量压缩需要串行处理并及时释放 Image、Canvas 和 Object URL。移动设备上应避免无意义的超大预览,并在按钮状态中明确提示处理正在进行。
LemonArt 选择浏览器原生能力完成常规压缩,不需要上传接口,也不依赖账户系统。对于专业工作流,用户仍应保留原始文件,并在发布前检查文字、边缘和透明区域是否因有损编码发生变化。
五、批量压缩的队列与内存
批量压缩的稳定性来自“一个接一个”而不是“一起上”。LemonArt 对文件列表按序处理:每张图先解码到 Image,绘制到 Canvas,编码成 Blob,记录结果并更新行内进度,然后立即释放临时资源,再处理下一张。同一时刻最多存在一张原图和一份结果,把内存峰值压到单图级别。
失败隔离是队列的另一半:单张图片解码失败(超分辨率、损坏文件、不支持的色彩配置)只标记该行为 error 并显示原因,不中断后续任务;用户清空列表或移除单行时,对应的 Object URL 会被 revoke,避免长会话中资源只增不减。
for (const item of files) {
item.status = 'converting'
try {
const blob = await compressOne(item) // 解码 → Canvas → 编码
item.resultBytes = blob.size
item.status = 'done'
} catch (error) {
item.error = String(error) // 单行失败,不中断队列
} finally {
URL.revokeObjectURL(item.url) // 每项处理完立即释放
}
}ts要点总结
- 压缩策略应同时考虑格式、质量和目标像素尺寸。
- 用真实 Blob 大小反馈结果,不把质量百分比当成绝对指标。
- 串行任务、释放资源和本地处理是浏览器压缩的稳定性基础。
- 失败隔离与逐行状态反馈,让批量任务在出现坏图时依然可控。