图片压缩后还是很大?我把 812 张图从 1.21GB 压到 178MB 的完整过程和参数
我那个博客的 img 目录,去年年底统计是 812 张图、1.21 GB。最大的一张 6.8 MB,iPhone 拍的 HEIC 转出来的。托管商流量账单看得我心慌,就想着干脆一次性全压一遍。
第一轮我干的事儿挺蠢的:打开 Photoshop,录了个动作,"存储为 Web 所用格式",质量拉到 60,批量跑。结果体积降到 760 MB 左右——降了不到 40%——但好几张渐变背景的图出现了明显色带,人像的皮肤开始一块一块。后来才搞明白问题出在两个地方:一是那个动作里我忘了关"转换为 sRGB",原本带 Adobe RGB 的图被错误映射了;二是质量 60 对 JPEG 来说已经踩到底线,尤其 4:4:4 采样在这个质量下会被编码器强行降到 4:2:0。
这一轮最大的教训其实不是参数,是我压根没想过"这些图最终会被用在哪"。博客正文插图在 Retina 屏上最宽也就显示到 800 CSS px,我给它的却是 6000 px 宽的原图。多出来的像素,浏览器下载了、解码了、然后扔掉了。纯浪费。
先定规格,再谈工具
我最后定下来的是三档输出,每档对应一个明确的显示场景。这个表我贴在显示器边上,改图之前先看一眼:
| 用途 | 长边像素 | 格式 | 质量参数 | 单张目标体积 |
|---|---|---|---|---|
| 列表页缩略图 | 400 | WebP | q78 | < 25 KB |
| 正文插图 | 1600 | WebP | q82 | < 200 KB |
| 灯箱 / 原图 | 2560 | AVIF + JPEG 兜底 | q60 / q85 | < 400 KB |
为什么要卡"单张目标体积"这一列?因为质量参数是个相对值,同一张 q82 的图,纯色背景可能只有 30 KB,密林照片能干到 500 KB。有个绝对值的锚点,你才知道哪张图明显压失败了、需要单独回炉。
三个工具的参数,我一个一个试过来的
cwebp(命令行,批量化最省心)
cwebp -q 82 -m 6 -mt -af -sharp_yuv -resize 1600 0 in.jpg -o out.webp
-m 6 是压缩方法,0 最快 6 最慢,体积差大概 8%~12%,我一般给到 6,反正跑批的时候我在吃饭。-af 是自动过滤,-sharp_yuv 在 YUV 降采样时保留锐度——这两个开关加上去,同样 q82 能多留住一点边缘细节。
ImageMagick(JPEG 兜底图用)
magick mogrify -path ./out -auto-orient -resize '2560x2560>' \
-strip -sampling-factor 4:2:0 -quality 85 -interlace Plane ./src/*.jpg
'2560x2560>' 那个大于号是关键,意思是"只缩不放",小图不会被拉大。-interlace Plane 是渐进式 JPEG,首屏能先看到模糊轮廓再逐步清晰,体感快很多,成本只有 1~2 KB。
注意 -strip 会连 ICC 色彩配置一起删掉。如果你的图是 Adobe RGB 或者 ProPhoto RGB 拍的,删之前一定先 -colorspace sRGB 转一次,不然颜色会明显发闷。我第一次就栽在这。
sharp(Node 脚本,适合塞进构建流程)
const sharp = require('sharp');
sharp('in.jpg')
.rotate() // 必须放在 resize 之前
.resize(1600, null, { withoutEnlargement: true, fit: 'inside' })
.webp({ quality: 82, effort: 5, smartSubsample: true })
.toFile('out.webp');
.rotate() 那个位置千万别搞错。手机竖拍的图,像素其实是横的,靠 EXIF 里 Orientation=6 让看图软件自动转过来。你要是不先转就 resize,缩完再想转,尺寸和方向就全乱了。我在这上面浪费了半个下午,一度以为是 sharp 的 bug。
AVIF 到底值不值得用
拿同一张 4000×3000 的风景原图实测(压缩到 1600px 宽):
- 原始 JPEG (q95):5.4 MB
- JPEG q82 / 4:2:0:218 KB
- WebP q82:154 KB
- AVIF q60:96 KB
- AVIF q50:78 KB,但天空开始出现轻微块状
体积上 AVIF 确实赢麻了,比 WebP 再小 35% 左右。但代价在编码时间上:同一台 M1 MacBook Air,单线程,WebP 大约 0.4 秒/张,AVIF 大约 4.7 秒/张。812 张图,WebP 跑完 5 分多钟,AVIF 得跑一个多小时。
所以我的做法是混合:缩略图和正文图统一 WebP,只有首页那几张门面大图用 AVIF,配合 <picture> 做兜底。
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" width="1600" height="900" alt="封面" loading="lazy">
</picture>
width / height 一定要写,不写就是给自己找 CLS 抖动。这个跟压缩没关系,但顺手一起说了。
四个特别容易翻车的点
1. DPI 是骗人的。 我见过有人在 PS 里把 72 dpi 改成 300 dpi,然后盯着文件大小看,一 KB 都没变。DPI 只是元数据,告诉打印机每英寸打多少个点,屏幕上显示只认像素数。想让图变大,只有改像素这一条路。
2. PNG 不等于"无损所以更好"。 一张 1200px 宽的截图,PNG 24 位可能 800 KB,转 WebP 有损 q80 只要 60 KB,肉眼几乎分不出。但如果图里全是文字或者细线框图,WebP 有损会把字笔画边缘啃出毛刺,这时候老实用 PNG 8 位,或者 cwebp -lossless。
3. EXIF 方向。 前面说过,-strip 删掉 Orientation 之后图就躺平了。ImageMagick 用 -auto-orient,sharp 用 .rotate(),exiftool 也能单独重置:exiftool -Orientation=1 -n *.jpg。
4. CMYK 的图。 找印刷厂要来的设计稿经常是 CMYK,直接往 WebP 转颜色会整个偏。先过一道:magick in.tif -colorspace sRGB out.jpg。
我最后定下来的流水线
三层目录:src/ 放原图永远不删,out/thumb、out/web、out/full 分别是三档产物。一个 build.sh 跑完:
#!/bin/bash
set -e
mkdir -p out/thumb out/web out/full
# 缩略图 400px
magick mogrify -path out/thumb -auto-orient -resize '400x400>' \
-strip -quality 78 -sampling-factor 4:2:0 -interlace Plane src/*.jpg
# 正文图 1600px WebP
for f in src/*.jpg; do
base=$(basename "$f")
cwebp -quiet -q 82 -m 6 -mt -af -sharp_yuv -resize 1600 0 "$f" -o "out/web/${base%.*}.webp"
done

# 原图兜底 2560px JPEG
magick mogrify -path out/full -auto-orient -resize '2560x2560>' \
-strip -quality 85 -sampling-factor 4:2:0 -interlace Plane src/*.jpg
最终结果:812 张原图 1.21 GB,out 目录合计 178 MB。正文页首屏加载从 3.2 秒降到 1.1 秒(Chrome DevTools 的 Slow 4G 节流,1.6 Mbps 下行)。
另外提一句,magick mogrify 是会覆盖原文件的,如果你 -path 写错目录,原图当场蒸发。我第一次跑的时候把 -path out/thumb 手滑写成 -path src/,还好那批图在 git 里。现在我的 src 目录是只读的,chmod -R a-w src/,多一道保险。
一点个人看法
网上教程一上来就教你"用 XX 工具一键压缩,省 80% 体积"。这话本身没错,但省下来的那 80% 和画质之间怎么权衡,没人能替你做决定——这恰恰是整件事里唯一需要动脑的部分。
我的判断标准特别土:把压完的图丢到手机上,离脸 30 厘米看,看不出色块就算合格。因为绝大多数读者就是这么看图的。你在 27 寸 4K 屏上放大到 200% 挑出来的那点噪点,真没人在乎。
真正毁掉体验的不是画质损失 3%,是首屏白屏转了三秒。