我是怎么把页面图片流量砍掉一半的

上个月接到一个需求,产品说"首页加载太慢了,能不能优化一下"。

我打开 Network 面板一看------光图片请求就占了整个页面流量的 70%,最大的一张 PNG 图标居然有 380 多 KB。我当时就觉得,这不是优化问题,这是没做优化。

花了两天时间,把图片这块从头捋了一遍。整理成这篇文章,分享给同样在做性能优化的你。


第一步我踩的坑:格式没选对

我之前有个习惯,截图默认存 PNG,觉得"无损嘛,质量好"。直到我看到一张商品照片被存成 PNG 后有 1.2MB,换成 JPEG 只要 80KB------质量肉眼完全看不出差别,我才意识到格式选错有多浪费。

现在我的选格式思路很简单:

照片类用 JPEG。有损压缩,但人眼对高频细节本来就不敏感,压缩后视觉损失很小。Banner、商品图、背景图,全上 JPEG。唯一要注意的是,JPEG 不支持透明度,带透明背景的图别用它。

图标和透明图用 PNG。支持 Alpha 通道,适合 Logo、图标这类需要透明背景的场景。但 PNG 体积大,彩色照片千万别用 PNG 存。另外 PNG 有两个版本要分清楚:PNG-24 是真彩色,PNG-8 最多 256 色但体积更小,颜色简单的图标用 PNG-8 就够了。

动画用 GIF。GIF 只支持 256 色、全透明(没有半透明),色彩表现很差,但它目前还是最通用的动画格式。Loading 图、小动画图标,暂时还离不开它。

新项目直接上 WebP。这是 Google 推出的现代格式,无损比 PNG 小 26%,有损比 JPEG 小 25%~34%,还同时支持透明和动画。2024 年主流浏览器全面支持,我现在新项目的图片默认都往 WebP 转。

以我们那个项目为例,焦点图换成 WebP,图标保留 PNG,动画 Logo 用 GIF------光是格式这一步,总流量就降了将近 30%。


第二步:压缩,但要用对工具

格式选对之后,我开始批量压缩图片。不同格式对应不同工具,千万别乱用。

PNG 压缩:node-pngquant-native

我试过好几个 PNG 压缩工具,最后留下来的是 node-pngquant-native,跨平台,对 PNG-24 格式压缩效果特别好。

java 复制代码
npm install node-pngquant-native
ini 复制代码
const pngquant = require('node-pngquant-native');
const fs = require('fs');

const input = fs.readFileSync('./in.png');
const output = pngquant.compress(input);
fs.writeFileSync('./out.png', output);

就这几行代码,我把那张 383KB 的 PNG 图标压到了 55KB,压缩比 85%,视觉上完全看不出差别。这个结果当时让我有点惊讶,之前真的是在白白浪费流量。

JPG 压缩:jpegtran

JPEG 的压缩我用 jpegtran,它做的是无损优化------去掉 EXIF 元数据、优化编码结构,不会影响图片质量。

复制代码
npm install jpegtran
objectivec 复制代码
jpegtran -copy none -optimize -outfile out.jpg in.jpg

压缩幅度大概在 10% 左右(88KB → 79KB),不算夸张,但胜在稳定无损,批量处理生产环境图片很放心。

GIF 压缩:gifsicle

GIF 动画用 gifsicle,通过调整帧比例和裁剪透明区域来瘦身:

ini 复制代码
# 基础优化
gifsicle --optimize=3 -o out.gif in.gif

# 带透明裁剪
gifsicle --optimize=3 --crop-transparency -o out.gif in.gif

我们项目里一个 Loading 动画从 94KB 压到了 88KB,不算大,但重要的是无失真,透明效果保留得很好。


第三步:加载策略,这才是拉开差距的地方

压缩是静态优化,做完就固定了。但我后来发现,真正能拉开性能差距的是加载策略------根据用户的设备和网络,动态决定给他加载什么。

根据网络环境切换图片尺寸

弱网用户加载 1000×1000 的大图,体验很差。我们的做法是检测网络类型,拼不同尺寸的图片 URL:

ini 复制代码
const connection = navigator.connection;
const size = connection.effectiveType === '4g' ? '1000x1000' : '100x100';
const imgUrl = `https://img.example.com/product_s${size}.jpg`;

WiFi 给大图,4G/3G 给小图,流量节省非常明显。这个思路在京东的图片 CDN 上也能看到,URL 里带着 s100x100 这样的尺寸参数。

响应式图片:srcset

不同屏幕密度的设备,加载的图片分辨率也应该不同。我现在习惯用 HTML5 原生的 srcset 属性:

ini 复制代码
<img
  srcset="img-320w.jpg 1x, img-640w.jpg 2x, img-960w.jpg 3x"
  src="img-960w.jpg"
  alt="产品图"
>

浏览器会根据设备像素比自动选合适的那张,不支持 srcset 的老浏览器会回退到 src,兼容性很好,几乎零成本。

渐进式加载:别让用户盯着空白

图片没加载出来的时候,空白区域体验很差。我用过三种占位方案,分享一下各自的感受:

统一占位符:直接放一张 Logo 或 loading 图标。实现最简单,但"所有图都长一样"有点奇怪,适合内容列表场景。

LQIP(低质量图像占位符) :先展示一张模糊的低质量预览,加载完成后替换为高清原图,Medium 那种图片加载效果就是这个思路。

复制代码
npm install lqip
ini 复制代码
const lqip = require('lqip');

lqip.base64('./product.png').then(base64 => {
  // 先用 base64 作为 img src 显示模糊预览
  // 异步加载真实图片后替换
  console.log(base64);
});

SQIP(SVG 占位符) :用几何图形近似原图生成 SVG,效果比 LQIP 更好,而且 SVG 可以无损缩放。

ini 复制代码
const sqip = require('sqip');

const result = sqip({
  filename: './product.png',
  numberOfPrimitives: 10
});

三个方案我都用过。日常列表页我用 LQIP 就够了,重要的详情页或者 Banner 我会上 SQIP,效果确实更细腻。

停下来问自己:这里真的需要图片吗?

这是我养成的一个习惯------在放图片之前先想一秒:有没有更轻量的替代方案?

  • 小图标 → Web Font(iconfont) ,任意缩放不模糊,一个字体文件搞定所有图标
  • 简单的小图 → Data URI 内联,直接嵌进 HTML,减少一次 HTTP 请求
  • 一堆小图标 → CSS Sprite(雪碧图) ,合并成一张图,请求数从 N 次降到 1 次

我们项目里有十几个状态图标,之前是十几个 PNG,换成 iconfont 之后图标请求直接消失了,字体文件也才几十 KB。


终极方案:让服务器帮你自动做这些

上面这些都是"手动优化",做一次固定下来。但如果你的项目图片量很大,或者有用户上传图片的场景,我建议考虑服务器端图片自动化

思路很简单:在图片 URL 上加参数,服务器识别参数后实时返回对应版本的图片。

ini 复制代码
# 缩放到 500x500
https://img.example.com/product.jpg?s=500x500

# 转成 WebP
https://img.example.com/product.webp

# 质量压缩到 30%
https://img.example.com/product.jpg?q=30

服务器能处理的事情很多:裁剪缩放(按长边/短边/填充/拉伸,甚至智能识别主体位置)、格式互转(JPG/PNG/GIF/WebP 随时切换)、质量动态调节、加水印、高斯模糊......

前端只需要在 URL 上拼参数,剩下的服务器搞定。这套方案在大厂几乎是标配,自己搭的话可以把 node-pngquant-nativejpegtrangifsicle 这些工具封装成服务,用 Nginx 或者 Node.js 中间件来拦截请求、解析参数、执行处理。

更进一步还可以接 AI 能力:鉴黄、敏感内容识别、智能抠图、智能排版------如果你在做 UGC 平台,用户上传的图片走这套流水线,可以省掉大量人工审核和手动处理的成本。


最后说两句

把这些做完之后,我们项目的图片总流量降了将近一半,首屏加载时间也从 4s 多降到了 2s 以内。产品说"感觉快多了",我说"其实主要是图片的问题"。

如果你也在做性能优化,我的建议是按这个顺序来:

格式选对 → 工具压缩 → 加载策略 → 服务器自动化

每一步都能看到明显收益,不用一次全做,先从格式和压缩入手,立竿见影。

有问题欢迎评论区聊。

相关推荐
木叶子---2 小时前
前端打包出错
前端·人工智能·tensorflow
JAVA面经实录9172 小时前
前端系统化学习计划表(含完整知识思维导图)
前端·学习
本末倒置1832 小时前
开发了一个所见所得的md编辑器,致敬Typora大佬
前端
kyriewen3 小时前
TypeScript 高级类型:我用 infer 写了一个类型安全的 EventBus,终于搞懂了泛型约束
前端·javascript·typescript
UXbot3 小时前
原型设计工具如何帮助新人快速进入产品行业?
前端·低代码·ui·交互·团队开发·原型模式·web app
黄敬峰4 小时前
从 DFS 遍历到抖音推荐算法:前端工程师的硬核复习笔记
前端
zach4 小时前
网页中的虚拟滚动技术是不是源自IOS中的tableview的机制
前端
林希_Rachel_傻希希4 小时前
1小时速通React之Hooks
前端·javascript·面试