前端图片压缩实战——我把网站图片体积砍了 70%

上个月接到一个任务,优化公司一个内容站的加载速度。Lighthouse 一跑,Performance 分数 38,最大的一张 banner 图 2.4MB,首屏加载 6 秒多。老板说"你给搞搞"。

说实话这种活以前也干过,但每次都做得比较糙------要么让设计重新导出小一点的图,要么直接在服务端挂个 ImageMagick 压缩。这次不一样,网站有好几百篇文章,每张文章配了好几张图,全让我手动处理一遍不太现实。

所以我需要一套能批量跑、或者最好让用户在上传前就自动压缩的方案。

先搞清楚问题在哪

用 Chrome DevTools 的 Network 面板看了一圈,问题主要集中在三类图片:

  1. 运营上传的文章配图,很多是直接从手机拍的照片,4000x3000 像素的 JPEG,单张 3-5MB
  2. 产品截图,PNG 格式,一张 1920x1080 的截图能有 800KB 到 1.5MB
  3. 一些老文章的封面图,格式混乱,JPG、PNG、WebP 都有

其实核心问题就一个:图片太大了,而且没有经过任何压缩处理就直接上了 CDN。

方案选型

我调研了几种常见做法:

方案一:服务端压缩

用户上传后在服务端用 Sharp(Node.js)或者 ImageMagick 压缩。这是最传统的做法,好处是处理逻辑集中、好控制。

坏处也很明显:得占服务器资源。我们那个小站用的是 2C4G 的机器,跑个 Node 服务本来就吃紧,再加上图片处理,高峰期 CPU 直接飙到 90%。而且上传大图片本身就要时间------用户传一张 5MB 的图,光上传就要好几秒,然后才开始压缩。

方案二:CDN 图片处理

阿里云 OSS 和七牛都支持图片处理参数,在 URL 后面加 ?x-oss-process=image/resize,w_800/quality,q_80 这种。确实方便,不用自己写代码。

但有个坑:CDN 处理是按次计费的,而且处理后的图片缓存策略需要额外配置。更关键的是,我们有些图是嵌在富文本里的 img 标签,URL 不带参数,要全量替换一遍比较麻烦。

方案三:浏览器端压缩

在用户上传图片之前,先在浏览器里把图压缩了。利用 Canvas API 或者 WebAssembly 解码器,可以在客户端完成压缩,上传到服务器的直接就是压缩后的小文件。

这个方案对我吸引力最大------不占服务器资源,上传速度也快了(因为文件变小了),而且可以从根源上解决问题。

我最后怎么做的

实际落地的时候我用了两套东西配合:

对于后台上传场景 ,我在编辑器里加了一个前端压缩步骤。用户选完图片,先用 browser-image-compression 这个库在浏览器里压缩一轮,然后再上传。核心配置大概长这样:

javascript 复制代码
import imageCompression from 'browser-image-compression';

async function compressBeforeUpload(file) {
  const options = {
    maxSizeMB: 0.5,
    maxWidthOrHeight: 1920,
    useWebWorker: true,
  };
  return await imageCompression(file, options);
}

这个库底层用的是 Canvas 重绘,JPEG 和 PNG 都能处理,压缩率大概在 60%-80%。一张 3MB 的手机照片压完大概 300-500KB,对网站展示来说完全够用。

对于存量图片和偶尔需要手动处理的图 ,我用了一个叫 Glazlo 的在线工具。它是纯浏览器端处理------图片不上传到任何服务器,全在本地完成。我选它的原因是:有些图片涉及产品截图,里面有内部界面信息,上传到第三方服务不太合适。Glazlo 的处理逻辑全跑在浏览器里,打开 F12 看 Network 面板确认过,确实没有往外发请求。

它支持指定目标文件大小,比如你可以设"压到 200KB 以内",它会自动调质量参数来逼近这个目标。这个功能在做微信公众号封面图的时候特别好用------公众号要求封面图不超过 2MB,但实际上压到 200KB 加载体验会好很多。

效果

搞完以后跑了一遍 Lighthouse:

  • Performance 分数从 38 升到了 72
  • Largest Contentful Paint 从 6.2s 降到 2.1s
  • 首页图片总大小从 4.8MB 降到 1.4MB

说实话这个结果比我预期的好。之前 70% 的体积减少主要是 JPEG 图片贡献的------手机拍的照片压缩空间太大了。PNG 截图的压缩空间相对小一些,大概减了 40% 左右。

几个踩过的坑

1. Canvas 压缩 PNG 会变透明

canvas.toBlob() 输出 JPEG 的时候会丢失透明通道,透明部分变成黑色。如果原图有透明度,要么保持 PNG 格式,要么在转 JPEG 的时候手动填充背景色。

2. WebP 兼容性

Safari 14 以下不支持 WebP。如果你的用户群还有大量 iOS 13 设备(我们做过统计,大概还有 5%),就别全站强制 WebP。做个格式降级:支持 WebP 就出 WebP,不支持就回退 JPEG。

3. 质量参数不是越低越好

JPEG 质量设到 50 以下,文字和线条边缘会出现明显的色块。对于产品截图这类包含文字的图,质量建议不低于 75。照片类可以压到 60 甚至更低,肉眼差别不大。

4. 别忘了一张图可能有好几个尺寸

响应式图片用 <picture> 标签或者 srcset 的时候,别忘了每个尺寸都要压缩。我一开始只压了原图,结果 400px 宽度的缩略图还是从 CDN 实时裁切的,白白浪费了一轮加载时间。

总结

图片压缩这件事,说复杂也复杂,说简单也简单。关键是找到适合自己项目场景的方案:

  • 如果图片量大、上传频繁,优先做前端压缩,从源头减负
  • 如果团队有运维资源,服务端压缩 + CDN 配合也很稳
  • 如果只是偶尔需要处理几张图,找个靠谱的在线工具就够了

不用追求极致压缩率,把 3MB 压到 300KB 就已经赢了 90% 的加载性能。剩下的 10% 留给以后慢慢优化就好。

相关推荐
张一凡931 小时前
把“不要乱改”从提示词里拿出来,放进可校验的提案里
前端·后端·架构
木子欢儿1 小时前
Debian 13 + vsftpd 搭建FTP服务
linux·服务器·前端·网络·debian
leeguoo1 小时前
让 Claude Code / Codex 用上你已登录的 Chrome
前端·chrome·ai编程
天蒙蒙亮2 小时前
打安卓apk包+“应用商城”发布
前端·uni-app
Lank_M2 小时前
视频压到指定大小总超一截?码率控制的误差拆开看
前端
用户2054555788002 小时前
飞书运费台账同步到 MySQL:单一策略落地教程
前端
LEE2 小时前
别把 Agent 架构画成组织架构图——子代理该不该拆,用任务级 Eval 说话
前端·后端·ai编程
梦醒江南2 小时前
RAG 证据链验收:别只测答案像不像,要证明它从哪里来
前端·agent·ai编程
费曼学习法2 小时前
无人值守AI内容矩阵实战(8):把标题 A/B 实验做成可判胜的流水线
前端