大多数 Web 应用仍然将图片上传视为一个“黑盒”。用户拖入一个文件,浏览器将其发送,服务器要么接受负载,要么抛出一个没人准备好的 413 错误。浏览器端压缩改变了这一局面。它让你有机会在数据传输前缩小负载,这意味着更快的上传速度、更低的带宽成本以及更少的服务器超时。但这项工作很容易出错。如果你把压缩当作一个标有“质量”的魔法滑块,你就会交付损坏的图片、拉伸的缩略图和令人困惑的用户体验。更好的方法是将整个流程视为一条流水线。

以流水线而非滑块的思维进行思考

将任务分解为离散的阶段。从 input 元素读取文件。将图像缩放到目标尺寸。编码一个新的 Blob。然后将结果渲染回用户。每个阶段只做一件事,并将输出传递给下一个阶段。这种分离不仅仅是为了代码更整洁,它还让单元测试变得简单。你可以将已知的缓冲区输入缩放阶段,而无需触碰文件输入。你可以验证编码器是否输出了小于 200 KB 的 JPEG,而无需等待服务器往返。当出现问题时,你确切知道是哪一步失败了。

保持这些职责分离还可以防止上传过程中的意外情况。如果你将缩放和编码捆绑在一个混乱的函数中,中途发生的解码错误可能会让你的上传队列处于不一致的状态。流水线强制你在每个边界进行验证。如果文件无法解码,你可以在创建 canvas 之前就捕获它。如果编码后的 Blob 太大,你可以在请求服务器存储之前就捕获它。

在编写代码之前定义契约

在任何人编写 canvas 绘制调用之前,先写下规则并与团队共享。选择接受的 MIME 类型。你会允许 JPEG、PNG、WebP 还是 AVIF?每种类型在 Alpha 通道、浏览器支持和 CPU 开销方面都有不同的影响。设置最大输入大小。如果你尝试在内存中完全解码一张来自旗舰手机的 30 MB 原始照片,它可能会导致旧笔记本电脑卡死或崩溃。定义最大输出尺寸。如果你的 UI 从不显示宽度超过 2048 像素的图像,那么就没有理由让 6000 像素宽的照片通过流水线。

最重要的是,要为解码失败做好计划。损坏的文件、奇特的色彩配置或截断的上传都可能导致 Image 构造函数抛出异常。你的流水线需要一个清晰的 catch 块和人类可读的错误消息。不要让浏览器默默地挂掉,让用户盯着加载图标却什么也没发生。

尊重图像

变形看起来很不专业。保持宽高比并限制长边。如果你的目标框是 1024 x 1024 像素,那么一张 4000 x 3000 的照片应该落在 1024 x 768 的尺寸上,而不是 1024 x 1024。根据长边计算缩放因子,让短边随之调整。这可以防止图像被拉伸成奇怪的形状。

对于实际的导出,使用 canvas 的 toBlob 方法。它让你直接控制输出格式和质量设置,并且它是异步运行的,因此不会阻塞主线程。创建一个离屏 canvas,将调整大小后的图像绘制到上面,然后使用你偏好的类型和质量值调用 canvas.toBlob。那个新的 Blob 就是你要交给上传逻辑或存储 API 的内容。

展示证据

压缩是隐形的工作。如果你不展示数据,用户就不会信任这个过程。构建一个允许他们对比原始文件与结果的界面。显示原始文件大小、新文件大小、新尺寸和最终格式类型。看到 4.2 MB 的手机照片降至 380 KB 的 WebP,可以消除用户担心你偷偷破坏他们图片的恐惧。

这种透明度也有助于故障排除。当用户抱怨上传失败时,你首先要检查的是输出尺寸是否超过了服务器限制,或者格式是否从 PNG 变成了 JPEG 并丢失了 Alpha 通道。将这些数据放入 UI 中,这样用户在提交支持工单之前就可以自行诊断问题。

预设优于重复压缩

永远不要对同一张图像进行两次压缩。每次通过有损编码器都会丢失更多细节并引入块状伪影。如果你让用户反复点击“优化”,第三代图像看起来就像复印件的复印件。相反,应从原始源文件生成每一个输出,并提供预设:

  • 较小的文件: 降低质量并严格限制尺寸,适用于缩略图或快速预览。
  • 平衡: 设定适中的质量水平和合理的尺寸,适用于社交媒体动态和图库。
  • 更多细节: 保持高画质并保留较大的尺寸,适用于摄影、艺术作品或打印预览。

在内存中存储原始 Blob,以便用户可以在预设之间切换,而不会产生逐层累积的质量损失。始终从源文件生成,绝不要从上一次的输出结果生成。

像用户上传那样进行测试

配备光纤连接和 32 GB RAM 的开发机并不代表现实情况。请使用真实用户携带的实际文件进行测试。来自 iOS 和 Android 的手机照片使用不同的元数据方向,并且可能源自 HEIC 格式。透明资产(如 Logo 和图标)在进行 JPEG 转换时表现不同,因为 JPEG 根本不支持 Alpha 通道。超大文件会暴露 2 GB RAM 设备上的内存限制。缓慢的移动端 CPU 会让你看清 toBlob 调用实际需要多长时间。

使用 Chrome DevTools 来限制 CPU 和网络带宽。尝试使用一部五年前的 Android 手机。如果你的流水线在编码时锁定了 UI 三秒钟,那么你需要将繁重的工作移至 Web Worker 中,以保持界面的响应能力。

先交付基础功能

支持每一种格式是很诱人的,而且