图片压缩后还是很大怎么办?我扒了200张网页图,发现大部分人第一刀就砍错了

关键词:图片压缩,图片优化,WebP,AVIF,网页图片加载慢

摘要:用200张真实网页图的测试数据说明:图片压缩的第一优先级不是调quality,而是按显示尺寸重导。含ImageMagick、sharp、picture标签的具体参数和无损WebP、二次保存、AVIF三个常见坑。

上周三晚上快十二点,小林发我个链接,说他那个烘焙工作室的官网「打开像在放 PPT」。我点进去,手机上白屏了大概四秒,然后一块一块往外蹦图。首屏,14 张图,加起来 12.7MB。

图片

我把他那些图全下下来,最大的那张 4032×3024,4.1MB。但它在页面上,是显示成一个 780px 宽的方块。也就是说用户下载了 4.1MB 的数据,只用到其中大概 3% 的像素。

接下来两周我断断续续扒了大概 200 张网页图,一部分是朋友的,一部分是我自己从几个电商站爬的。扒完发现一个问题:绝大多数人优化图片时,第一反应是去调那个 quality 滑块,从 90 拉到 80,再拉到 70,盯着预览图看半天。但真正吃掉体积的东西,根本不在那儿。

先把那个最反直觉的数字摆出来

我拿同一张 iPhone 拍的风景照做了组测试,原图 4032×3024,4.14MB。

先说尺寸。缩到 1560px 宽,也就是 780 的 2 倍、对应 DPR 2 的屏幕,quality 保持 85,出来是 312KB。一步,掉了 92%。如果缩到 780px 再存,是 186KB。

图片

再说 quality。都在 1560px 宽的前提下:q85 是 312KB,q75 是 208KB,q65 是 158KB。q85 到 q75 省三分之一,q75 到 q65 又省四分之一。

然后是采样。同一张图,1560px、q75,4:4:4 出来 268KB,改成 4:2:0 是 208KB,省 22%。这张图里有大片天空渐变,我放到 100% 看,肉眼基本分不出来。

最后是 EXIF。这张图 strip 掉元数据之后少了 41KB,不算多。但我见过一张人像模式拍的,EXIF 里塞了深度图,那一块就 640KB。

把收益排个序:缩尺寸省 90% 以上,调 quality 省 30% 左右,改采样省 20% 左右,删 EXIF 看运气。你会发现很多人花半小时纠结的那一刀,是全流程里收益最小的那一刀。

具体怎么做,四步

图片

第一步,先量,别先压。 打开浏览器 DevTools 的 Network 面板,刷新页面,按 Size 排序,看哪些图排在前三、分别多大。或者更直接,跑一遍 PageSpeed Insights,它会告诉你「适当调整图片大小,预计可节省 X KB」——那个 X 就是尺寸没缩对导致的浪费。这条提示出现的频率高得离谱,我扒过的那 200 张图里,有 137 张都在报这个。

第二步,定尺寸。 拿尺子量一下这张图在页面上实际占多少 CSS 像素宽。假设是 780px,那就导出 780(DPR 1)和 1560(DPR 2)两个版本,先别管 3x。3x 只在少数安卓旗舰上有收益,那部分流量占比通常不到 15%,为它多背一倍体积不划算。

第三步,选格式和参数。 照片类走 JPEG 或 WebP 有损;截图、图标、带文字的图走 PNG 或 WebP 无损。带文字的图千万别用 JPEG,字边缘会出现一圈灰蒙蒙的噪点,那是 DCT 变换的块效应,不是压缩质量的问题,你把 quality 拉到 100 也救不回来。

第四步,删元数据,写上宽高。 EXIF、ICC、内嵌缩略图全删,除非你要保留版权信息。然后 <img> 上一定要写 width 和 height,不然图加载完页面会往下跳一下,那个叫 CLS,是 Core Web Vitals 的一项。

三个我自己踩过的坑

坑一:给 JPEG 做「无损 WebP」,结果更大了。 这是我最想吐槽的一个。JPEG 本身就是有损的,它的噪点和振铃已经被烘进像素里了,你再用无损方式转 WebP,等于把那些噪点当成真实细节一丝不苟地保存下来。我测过的照片里,无损 WebP 比原 JPEG 大 15% 到 40% 的都有。无损 WebP 是给 PNG 用的,不是给照片用的。

图片

坑二:反复另存。 有个客户给我一张图,说改了五版。我打开一看,噪点堆得跟雪花屏一样。JPEG 每次保存都会重新做一次量化和反量化,它是可逆的——不是,是不可逆的。q80 存五次,画质跟 q40 差不多,但文件大小可能一点没降。所以永远从原图导出,别在成品上改来改去。

坑三:迷信 AVIF。 AVIF 确实猛,同画质下比 WebP 小 20% 到 30%,这个数字我复现过。但它编码慢,一张 1600px 的照片用 avifenc 默认参数要 3 到 8 秒,WebP 不到 0.5 秒。如果你在 CI 里批量跑,几百张图就是十几分钟的事。而且 Safari 到 16.4 才支持,虽然现在覆盖率够用了,但还是得用 <picture> 包一层降级。

代码就这几行

ImageMagick:

magick in.jpg -resize 1560x -sampling-factor 4:2:0 -strip -quality 82 -interlace Plane out.jpg

图片

-interlace Plane 是开渐进式,图片会从上往下逐渐清晰。实测同一张图渐进式大概大 5 到 8KB,但感知上快很多,值。

Node 里用 sharp:

sharp('in.jpg')
  .resize({ width: 1560, withoutEnlargement: true })
  .jpeg({ quality: 82, mozjpeg: true, chromaSubsampling: '4:2:0' })
  .toFile('out.jpg');

mozjpeg: true 会走 mozjpeg 编码器,同样画质下比 libjpeg-turbo 小 10% 左右,代价是编码慢一点,构建时能接受。

<picture>
  <source type="image/avif" srcset="a-1560.avif 1560w, a-780.avif 780w" sizes="780px">
  <source type="image/webp" srcset="a-1560.webp 1560w, a-780.webp 780w" sizes="780px">
  <img src="a-780.jpg" width="780" height="520" loading="lazy" decoding="async" alt="...">
</picture>

图片

<source> 的顺序不能反,浏览器取第一个它能解的格式。

最后说个我自己的看法

我觉得图片优化这事被讲复杂了。市面上大量教程在教你「如何选择最佳 quality 值」,用 SSIM、VMAF 去算,写一堆脚本。但真实网页里,绝大多数体积浪费来自「你放了一张比显示区域大五倍的图」,而不是「你把 quality 从 78 调到了 82」。

小林的站我最后改完,首屏从 12.7MB 掉到 1.9MB,手机上加载从 4.1 秒到 0.9 秒,那是 Lighthouse 在模拟 4G 下的数字。我做的事情里只有一件是关键:把那些图重新按显示尺寸导了一遍。其他都是附带的。

先量尺寸,再谈压缩。顺序反了,后面全是白忙活。

标签: