🚀 Web 图片优化实践:通过 AVIF/WebP 将 12MB 图片降至 4MB

背景

上一篇文章中提到了某活动 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>

在这个结构中,浏览器会按照以下顺序进行判断:

  1. 首先检查是否支持 AVIF 格式,若支持则加载 image.avif
  2. 若不支持 AVIF,则检查是否支持 WebP 格式,若支持则加载 image.webp
  3. 若以上两种格式均不支持,则回退到传统的 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 优化等手段,进一步提升加载速度与用户体验,并解决在不同浏览器环境下的兼容性问题。

相关推荐
用户916357440952 小时前
CSS中的"后"发制人
前端·css
小满xmlc2 小时前
react Diff 算法
前端
bug_kada2 小时前
Js 的事件循环(Event Loop)机制以及面试题讲解
前端·javascript
bug_kada2 小时前
深入理解 JavaScript 可选链操作符
前端·javascript
小满xmlc2 小时前
CI/CD 构建部署
前端
_AaronWong2 小时前
视频加载Loading指令:基于Element Plus的优雅封装
前端·electron
KallkaGo2 小时前
threejs复刻原神渲染(三)
前端·webgl·three.js
IT_陈寒4 小时前
Vue3性能优化:掌握这5个Composition API技巧让你的应用快30%
前端·人工智能·后端
excel13 小时前
在 Node.js 中用 C++ 插件模拟 JavaScript 原始值包装对象机制
前端