🚀 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 优化等手段,进一步提升加载速度与用户体验,并解决在不同浏览器环境下的兼容性问题。

相关推荐
wearegogog1235 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars5 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤5 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·5 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°5 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854056 小时前
CSS动效
前端·javascript·css
烛阴6 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪6 小时前
markstream-vue实战踩坑笔记
前端
南村群童欺我老无力.7 小时前
Flutter应用鸿蒙迁移实战:性能优化与渐进式迁移指南
javascript·flutter·ci/cd·华为·性能优化·typescript·harmonyos
C_心欲无痕7 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx