跳过正文
  1. 前端工程体验优化实战/

图片加载体积减少20%:自适应选择最优图片格式

hujiacheng
作者
hujiacheng
Front-end Developer / Strive To Become Better
目录
图片与懒加载 - 这篇文章属于一个选集。
§ 1: 本文

根据HTTPArchive网站十几年来的对海量网页的统计,网页中加载的图片类资源,体积中位数是1009 KB,请求数量中位数是21个。分别是页面总加载体积2419 KB的41.7% ,和总请求数量72个的29.5%。

可见图片是前端应用绝对的核心内容。

但是长久以来,前端应用的图片类资源面临着许多痛点,主要有:

  • 图片体积较大,加载耗时较长。
  • CDN流量开销较高:图片类资源数量多、体积大,是CDN费用的主要成本。

前端应用中图片类资源的加载也直接影响着用户体验,图片是首次内容绘制(FCP)和最大内容绘制 (LCP)指标的测量要素,所以从图片类资源入手优化用户体验,对前端应用不可或缺。

1. 图片格式对比
#

浏览器平台支持众多图片格式,分别有不同的特性,我们先通过对比了解一下各种格式:

格式简介和特性体积示例
(基于后文图片CDN计算)
发明年份浏览器兼容性
jpg- 最常见、应用最广泛的图片格式
- 体积一般,通常小于 png, gif 等格式
158 KB(100%)1992几乎所有浏览器都支持
png- 带有透明通道,支持图片部分透明
- 体积较大
819 KB(518%)1996几乎所有浏览器都支持
gif- 支持自动播放的动态图片
- 体积较大
423 KB(267%)1989几乎所有浏览器都支持
svg- 矢量图,任意缩放不影响清晰度
- 本质是一种标记语言,可以由浏览器解析渲染
- 体积视内容而定
2001Chrome 4 (2010年1月发布)以上版本支持
参考资料:https://caniuse.com/svg
webp- 支持动态图片
- 压缩率较高:支持有损压缩,无损压缩
- 专注于Web平台的表现
- 体积较小
136 KB(86%)2010Chrome 32 (2014年1月发布)以上版本支持
参考资料:https://caniuse.com/webp
avif- 支持动态图片
- 压缩率较高
- 体积较小
96 KB (60%)2019Chrome 85(2020年8月发布)以上版本支持
参考资料:https://caniuse.com/avif

通过上述表格对比不难看出,图片类格式明显分为2大类:

  • 传统图片格式:发明于二十年前的jpg、png、gif、svg
  • 现代图片格式:近十年来发明的webp、avif

这两类图片从功能上看:

  • 体积:传统格式显著大于现代图片格式。相较于jpg格式,webp格式的图片一般能减少10%左右的体积,avif格式更是能减少40%以上的文件体积。
  • 特性:现代格式都能覆盖传统格式的动态图片、无损压缩等功能和特性,传统格式特性较为单一,现代格式支持动态图片、无损压缩等特性更多。
  • 浏览器兼容性:兼容现代格式的浏览器占比相对略少。

如果能使用webp,avif等现代格式,对解决前端应用图片类资源体积较大,加载耗时较长、CDN开销较高的痛点会有明显优化。

但是浏览器兼容性又让我们无法放开手脚,不敢大胆采用现代格式。

所以有什么两全其美的解决方案吗?

2. 自适应选择最优图片格式方案:<picture>元素
#

性能和兼容性不可兼得的痛点,Web标准的制定者也早有共识,所以针对性地推出了<picture>元素致力于解决这一痛点。

<picture>元素允许同时引入多个图片格式的子元素,并根据浏览器的兼容性,按先后顺序、自适应加载其中1个格式的图片,实现所有用户根据自身兼容性,获取到最优图片格式。

请看代码示例:

<picture>
  <source type="image/avif" srcset="https://cdn.com/image.avif" />
  <source type="image/webp" srcset="https://cdn.com/image.webp" />
  <img src="https://cdn.com/image.jpg" alt="Example" />
</picture>

<picture>元素示例》在线运行DEMO:https://jsbin.com/xabenap/edit?html,output

在上述例子中,<picture>元素中包含了2个<source>元素和1个<img>元素。

每个<source>元素都包含一个srcset属性,2处srcset属性分别引用了avif, webp格式的图片URL,type属性则是格式对应的MIME类型

但是这段HTML只会触发avif一个图片资源的下载。

这是因为,对于<picture>元素,浏览器会按照从上到下的顺序检查<source>元素,加载第一个浏览器兼容格式的图片,并忽略后续的<source>元素和img元素。

例如,如果浏览器对各种图片格式都支持,那么上述例子就只会加载排序最靠上的avif格式图片,不会加载后续的webpjpg2种格式的图片。

如果浏览器对所有<source>声明的图片格式都不支持,它将降级到加载的<img>元素src属性对应的jpg格式图片,作为兜底方案。

<picture>必须声明一个img子元素,否则如果只有source元素,浏览器不会加载sourcesrc属性对应的图片资源。

也就是说,即使用户的浏览器既不支持解析avif, webp格式的图片,甚至不兼容<picture>元素,浏览器也会自动降级到使用<img>元素,确保始终有正确的图片加载。

因此,建议对<picture>元素中的<img>元素使用jpgpng等兼容性较广泛的图片格式作为src属性值。

注:<picture>元素的兼容性:

  • Chrome 38+(2014年发布,晚于支持webp,avif格式的版本)
  • Edge 13+(2015年发布)
  • Safari 9.1+(2016年发布)
  • IE不支持,会降级为加载<img>元素

参考资料:https://caniuse.com/?search=picture

<picture>元素经过业界大量实践检验,证明可用于生产环境。

例如,Bilibili视频网站,PC版首页的轮播图、封面图都大量使用了<picture>元素:

用上<picture>元素,终于能实现我们图片性能和兼容性两者兼得的需求了,可随之而来的难点是,如何方便高效地生成各种格式的图片,供<picture>元素使用呢?

3. 图片优化神器——图片CDN
#

工欲善其事必先利其器,图片CDN就是搭配<picture>元素的神器。

图片CDN是CDN的拓展功能,专用于处理图片类型的各种资源。各大云服务供应商都有提供图片CDN服务,除基础的资源存储外,还附带了多种能力,例如:

  • 格式转换、体积压缩
  • 尺寸缩放
  • 添加水印

这些能力使用起来非常方便,一般只需要修改图片的URL参数,即可实现自动对图片应用相关处理。

以腾讯云CDN的图片处理能力为例,他支持的能力和用法如下:

  1. 缩放图片:
图片类型体积URL链接
原图158 KBsample.jpeg
宽高缩放为原图50%46.2 KBsample.jpeg?imageMogr2/thumbnail/!50p
宽高缩放为指定50*50像素1.8 KBsample.jpeg?imageMogr2/thumbnail/50x50>!
  1. 图片格式转换:
图片类型体积URL链接
.jpeg(原图)158 KBsample.jpeg
.png819 KBsample.jpeg?imageMogr2/format/png
.webp136 KBsample.jpeg?imageMogr2/format/webp
.avif96 KBsample.jpeg?imageMogr2/format/avif

除此之外,还有添加盲水印,高斯模糊,获取图片主色调等众多实用功能,还可以进一步串联使用多种处理方式

注:各大云服务厂商的图片CDN文档:

使用这些图片CDN,不仅可以让我们方便地使用<picture>元素,还有助于提高日常工作中的开发效率和开发体验,进而让用户获得更好的体验。

例如:

  1. 利用格式转换能力,搭配上文中介绍的<picture>元素,实现优先选择加载avif,webp等现代格式图片,减小图片体积、加快加载速度,节省CDN流量。
  2. 利用尺寸裁剪能力,优化用户头像的加载,在加载头像缩略图时,用50px50px的小尺寸小图;在用户点击放大看大图时,再加载200200px的大图,从而节省流量。
  3. 利用获取图片主色调能力,为图片添加未加载完成时的背景色,避免页面中出现大片空白,为用户带来更好的浏览体验。

图片CDN辅助函数
#

为了便于在前端项目中调用图片CDN的处理能力,我们还可以添加一套辅助函数,例如:

// 预置一批可选格式值,例如缩放到75%,50%,25%
const ImgFormatOptions = {
    scaleTo75Percent: '!75p',
    scaleTo50Percent: '!50p',
    scaleTo25Percent: '!25p',
}

function getImgURL(name, options) {
    if (!name) {
        return ''
    }
    // 图片参数例子:?imageMogr2/thumbnail/!50p
    let url = `http://examples-1251000004.cos.ap-shanghai.myqcloud.com/${name}`
    if (!options) {
        return url
    }
    
    // 如果参数组合复杂,可以改为使用 URLSearchParams API
    // const searchParams = new URLSearchParams()
    // searchParams.set(option.key, option.value)
    //  return `${url}${searchParams.toString()}`
    
    let params = `?imageMogr2/`
    if (options.format) {
        params+=`format/${options.format}`
    } else if (options.thumbnail) {
        params+=`thumbnail/${options.thumbnail}`
    }
    // else if  ......

    return `${url}${params}`
}

基于这套辅助函数,我们就可以在前端的代码逻辑中,方便地使用图片CDN的各项能力,请看代码示例:

// 1. 格式转换
getImgURL('sample.jpeg', {format: 'avif'}) 
// http://example.myqcloud.com/sample.jpeg?imageMogr2/format/avif

// 2. 裁剪缩略图
getImgURL('sample.jpeg', {thumbnail: ImgFormatOptions.scaleTo50Percent}) 
// http://example.myqcloud.com/sample.jpeg?imageMogr2/thumbnail/!50p

还可以进一步和前端组件结合使用,封装出加载最优图片格式的<PictureImage>组件,React组件示例代码如下:

const ALL_IMAGE_FORMAT = ['avif', 'webp']; // 按顺序,优先加载靠前的格式

export default function PictureImage({ src, alt }) {
  return (
    <picture>
      {ALL_IMAGE_FORMAT.map((format) => {
        return (
          <source
            srcSet={getImgURL(src, { format })}
            type={`image/${format}`}
          />
        );
      })}
      <img alt={alt} src={src} />
    </picture>
  );
}

4. 验证量化和评估
#

1. 验证
#

1. 使用辅助工具验证图片格式
#

在图片CDN开启前,我们可以先在本地用图片格式转化工具,找一些前端应用中的图片,验证格式转换后的体积变化、视觉变化。

例如:

  • 使用在线的格式转化工具:https://convertio.co/zh/
  • 使用本地安装的 ffmpeg 转化图片格式:
ffmpeg -i input.jpg -c:v libaom-av1 output.avif

2. 确认图片CDN开关、收费及限制条件
#

图片CDN一般是需要独立开启的CDN服务,使用前记得检查,是否已经对指定的CDN域名开启了图片处理功能。

此外,部分CDN供应商,对特殊的图片处理(例如盲水印)有专门的计费规则,使用前一定确认一下,避免被意外扣费:

最后,图片CDN的处理能力一般都对原图片的尺寸、体积有最大限制,超过这一限制,就不会生效,使用前也应该加以注意,兼容这些边界情况。

2. 量化
#

1. 图片类资源加载总体积指标
#

我们可以继续复用第5节中介绍的《加载资源总体积指标》,基于Performance API的transferSizeencodedBodySize属性和initiatorType获取图片类资源的体积,并在Grafana中修改查询语句为sum(ResourceSize{type="Image"}),过滤出图片类资源的体积,做出如下可视化图表:

如果,页面和图片数量较多,导致上报数据过多,推荐使用第3节介绍的采样率,在保证量化准确性的基础上,减少上报数据量,节省用户网络流量,降低服务器负担。

2. CDN流量开销
#

图片体积相关的优化对CDN的流量开销也会有显著影响,因为图片往往是CDN中占比最大的资源,通过我们的优化体积变化后,预期也会直观的反应在CDN的流量费用上。

我们可以通过CDN供应商的管理后台,查询CDN账单,对比图片格式优化前后的CDN流量费用,用来量化图片优化的效果。

3. FCP和LCP指标
#

图片是FCP指标测量的首次渲染内容和LCP指标测量的最大元素的候选项,所以对图片体积相关的优化,也应该使用FCP和LCP作为量化指标。

在生产环境中,更推荐使用我们在第2节介绍的堆叠百分比评分图,稳定、准确地量化这2项指标,避免极端值导致的异常波动。

3. 评估
#

通过我们上述介绍的自适应选择最优图片格式优化,预期能让前端应用加载的图片类资源体积显著减少,优化后大多数用户从加载jpg,改为加载webpavif格式的图片,加载的图片体积预计能减少20%至40%。让图片类资源加载总体积指标在优化后,平均值、最大值、最小值都有显著下降。

同时,CDN流量花费预期也会因为图片体积减少,而有所下降,为我们带来真金白银的优化收益。

此外,如果图片类资源是FCP和LCP指标的测量目标,FCP和LCP的堆叠百分比评分图中,评分为优的占比,预期也会在优化后显著增加,例如下图 LCP评分百分比在11-12图片格式优化上线后,评分为优的占比,有显著的增长,如下图表现:

image.png

小结
#

这一节中,我们首先了解了各种图片格式的特性和体积表现。

随后,为了解决图片类资源长期以来体积大、加载慢、耗费流量的痛点,学习了用<picture>元素实现自适应选择最优图片格式方案

以及在开发实践中,使用辅助函数、配套组件方便高效地使用图片CDN的经验。

图片与懒加载 - 这篇文章属于一个选集。
§ 1: 本文

相关文章