背景
上一篇文章中提到了某活动 H5 的字体优化(🚀 Web 字体裁剪优化实践:把 42MB 字体包瘦到 1.6MB),该活动首屏还涉及到较大体积的图片资源加载(约 12.2 MB,其中 11.4MB 是预加载的动效图片)。这么大体积的图片与字体资源同时加载,对用户网络质量是比较大的考验,并且已经有部分用户反馈图片加载较慢,因此图片也需考虑 "瘦身"。
常规解决方案
一般情况下,前端开发主要使用<img>
标签及 PNG/JPG 格式,再一定程度结合图片压缩、懒加载、CDN(多节点分发、缓存预热)等优化方案来满足性能要求。部分 CDN 平台还提供一站式图片解决方案,例如图片压缩、裁剪、格式转换等功能。
其中格式转换功能通过识别浏览器请求头 Accept
(如:Accept:image/avif,image/webp,...
),智能下发 AVIF/WebP 等格式图片,以实现更优的压缩率和更快的加载速度。这对开发而言是符合标准且无需额外工作量的终极方案,但该功能尚未普及,因此本文将介绍其他实现相同效果的方式。
AVIF/WebP 简介
WebP 格式
WebP(Web Picture Format)是由 Google 推出的一种现代图片格式,提供有损和无损压缩两种模式。与传统的 PNG 和 JPG 格式相比,WebP 在保持相同视觉质量的情况下,可显著减少文件大小:
- 相比 PNG,WebP 无损压缩可减少 26% 的文件大小;
- 相比 JPG,WebP 有损压缩可减少 25-34% 的文件大小。
WebP 还支持动画和透明通道,是 GIF 和 PNG 的理想替代方案。目前,WebP 已得到所有现代浏览器的支持(IE 浏览器和部分旧版本移动端浏览器除外)。
AVIF 格式
AVIF(AV1 Image File Format)是基于 AV1 视频编码的新一代图片格式,压缩效率高于 WebP:
- 相比 WebP,AVIF 可减少约 20% 的文件大小;
- 相比 PNG/JPG,AVIF 可减少约 50% 的文件大小。
AVIF 支持高动态范围(HDR)和广色域,色彩表现更丰富。但目前 AVIF 的浏览器支持度弱于 WebP,主要在较新版本的 Chrome、Firefox 和 Opera 等主流浏览器中可用。
在实际使用中,可通过调整压缩参数进一步提升压缩率,以下是兼容性汇总图:
使用 Picture 标签实现图片优化
Picture 标签应用
HTML5 的<picture>
标签允许我们为不同的显示场景提供多个图片源,浏览器会根据当前环境自动选择最合适的图片进行加载。其基本结构如下:
html
<picture>
<source srcset="image.avif" type="image/avif" />
<source srcset="image.webp" type="image/webp" />
<img src="image.png" alt="img" />
</picture>
在这个结构中,浏览器会按照以下顺序进行判断:
- 首先检查是否支持 AVIF 格式,若支持则加载
image.avif
; - 若不支持 AVIF,则检查是否支持 WebP 格式,若支持则加载
image.webp
; - 若以上两种格式均不支持,则回退到传统的
image.png
。
已知问题
虽然<picture>
的兜底策略比较完善(即使 AVIF/WebP 格式或<picture>
/<source>
标签不被浏览器支持,仍可降级到<img>
处理),但因各浏览器厂商实现方式不同,依然存在部分问题。
问题 1:iOS 14.3、14.4 版本的 Safari WebP 兼容性问题
bugs.webkit.org/show_bug.cg...
在 iOS 14.3、14.4 版本的 Safari 中,<picture>
标签会尝试使用 WebP 格式渲染,但图片无法正常显示。需额外通过 JavaScript 检测实际是否支持 WebP 格式,示例如下:
tsx
// WebP 支持检测函数
const checkWebP = () => {
return new Promise<boolean>((resolve) => {
const img = new Image();
img.onload = () => resolve(img.height === 160);
img.onerror = () => resolve(false);
img.src =
"data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvn8AnAAdQiUpUov+BiOh/AAA=";
});
};
// 使用示例(简单版,无缓存、占位图等优化机制)
const Image = () => {
const [supportWebp, setSupportWebp] = useState(false);
useEffect(() => {
const checkSupportWebP = async () => {
const isSupported = await checkWebP();
setSupportWebp(isSupported);
};
checkSupportWebP();
}, []);
return (
<picture>
{supportWebp && <source srcset="image.webp" type="image/webp" />}
<img src="image.png" alt="img" />
</picture>
);
};
不过站在 2025 年来看,iOS 14.3、14.4 版本的占比可能不到 1%,因此可根据实际情况考虑是否兼容这两个版本的 Safari。
问题 2:部分 Safari 版本同时加载<source>
和<img>
资源 github.com/facebook/re... github.com/vuejs/core/...
问题表现如图:

社区解决方案如下:
tsx
// 方案一:把 <img> 标签放在 <source> 标签的前面
<picture>
<img src="image.png" alt="img" />
<source srcset="image.webp" type="image/webp" />
</picture>;
// 方案二:只保留 img 标签
{
isSafari ? (
<img src={supportWebp ? "image.webp" : "image.png"} alt="img" />
) : (
<picture>{/* some things */}</picture>
);
}
问题 3:Firefox 93-112、Safari 16.1-16.3 会将 AVIF 动图解析为静态图片,无动画效果 bugzilla.mozilla.org/show_bug.cg...
github.com/mozilla/sta...
解决方式同问题1,通过 JavaScript 检测是否完整支持 AVIF 格式,以决定是否使用 AVIF 图片。
tsx
// AVIF 支持检测函数
const checkAVIF = () => {
return new Promise<boolean>((resolve) => {
const img = new Image();
img.onload = () => {
const supportAvif = img.width === 1;
resolve(supportAvif);
};
img.onerror = () => resolve(false);
// avif 动图 base64 编码,内容省略
img.src = "data:image/avif;base64,...";
});
};
// 使用示例
const Image = () => {
const [supportAvif, setSupportAvif] = useState(false);
useEffect(() => {
const checkSupportAVIF = async () => {
const isSupported = await checkAVIF();
setSupportAvif(isSupported);
};
checkSupportAVIF();
}, []);
return (
<picture>
{supportAvif && <source srcset="image.avif" type="image/avif" />}
<img src="image.png" alt="img" />
</picture>
);
};
ImageX SDK
为解决上述问题并提升代码复用性,我们可能需要自行开发 Image 组件。不过 字节/火山引擎的 ImageX 服务已提供 @volcengine/imagex-react 插件,不仅解决了相关问题且功能更丰富(包括占位图、懒加载、SSR、自适应布局等),可以进行使用。
以下是一个简单的使用示例:
tsx
import { ImageXDomains, ImageFormat } from "@/common/consts";
import { ImageLoader, Viewer, ViewerProps } from "@volcengine/imagex-react";
export const Image = (props: ViewerProps) => {
const imageLoader: ImageLoader = ({ format, extra }) => {
const { domain = "", origin } = extra;
/**
* 把 https://p1.dailygn.com/obj/g-marketing-act-assets/static/media/bg.png
* 转换为 https://p1.dailygn.com/img/g-marketing-act-assets/static/media/bg.png~tplv-obj.avis/awebp
*/
if (ImageXDomains.includes(domain)) {
const { pathname } = new URL(origin);
const formatSrc = pathname.replace(/^/obj//, "/img/");
return `//${domain}${formatSrc}~tplv-obj.${format}`;
}
return origin;
};
return (
<Viewer
loader={imageLoader}
formats={[ImageFormat.Avif, ImageFormat.Webp]}
{...props}
/>
);
};
输出 HTML 结构:
html
<picture>
<source type="image/avif" srcset="//p1.dailygn.com/img/g-marketing-act-assets/static/media/bg.png~tplv-obj.avis">
<source type="image/webp" srcset="//p1.dailygn.com/img/g-marketing-act-assets/static/media/bg.png~tplv-obj.awebp">
<img src="//p1.dailygn.com/img/g-marketing-act-assets/static/media/bg.png~tplv-obj.image" >
</picture>
通过 imageLoader
函数,我们可以动态生成图片 URL,以便通过 CDN 下发不同格式、宽度、质量的图片资源。即使在没有使用 ImageX 服务的情况下,也能通过 format
参数适配已有的图片资源。
图片优化结果
通过上述优化,该活动的图片体积显著降低,加载速度也明显加快,具体数据如下:
![]() |
![]() |
![]() |
---|
压缩率及加载速度汇总:
格式 | 图片大小(KB / MB) | 压缩率 | 加载耗时(4G) | 加载速度提升 |
---|---|---|---|---|
PNG | 12506KB / 12.21MB | -- | 18.37s | -- |
WebP | 7355KB / 7.18MB | 41% | 13.3s | 27.6% |
AVIF | 4442KB / 4.33MB | 64% | 10.79s | 41.3% |
总结与思考
随着新技术的发展,图片优化方式也逐渐多种多样,但目前还没有普及的银弹方案。前端开发仍然要了解相关技术细节及坑点,以便实现可靠的程序。
通过 AVIF/WebP 等现代格式的应用及浏览器兼容性检测,可以在保持图片质量的同时,将体积减少约 50%,明显提升加载速度。
在实际应用中,还可以结合多格式降级策略、懒加载及 CDN 优化等手段,进一步提升加载速度与用户体验,并解决在不同浏览器环境下的兼容性问题。