在 CRA 中使用 webp 图片提升加载性能

webp 是 google 提倡的一种新的 image 格式,意在为 web 提供体积更小的图片格式。通常情况下,无损压缩可以减小 25%-35% 的体积(有例外情况,反而会增大体积,但是是因为转换图片格式不兼容引起的),有损压缩最大可以节省大约 75%-90% 的体积。

兼容性

使用新的浏览器特性,首先应该考虑兼容性问题,它的兼容性如下图:

image.png

可以发现,除了 ie 和 safari 之外,基本都支持了该格式,而且 safari 14 也即将支持该格式,到目前为止,全球浏览器的 ~75.9%(粗略统计) 份额的浏览器均可使用该功能。

如何判定兼容性

https://github.com/DonRai/react-image-webp/blob/master/modules/utils/index.js

核心代码如下:

const el = document.createElement('canvas') el.toDataURL('image/webp').indexOf('data:image/webp') === 0;

如果浏览器支持 ​webp​ 这种 ​mime-type​ 的话,则输入的 ​base64 字符串会包含特定的关键字(这种手段也可以用来检测浏览器是否支持别的格式)。

js 解决方案

由于可以通过 js 来判定浏览器是否支持该特性,所以问题也很好解决,只需要做一个逻辑判定即可,比如:

{  isWebpSupported() ? 
    <img src={require('./path/to/img.webp')} /> : 
    <img src={require('./path/to/img.png')} /> }

html 解决方案

另一种解决方案是,我们把图片的选择逻辑,委托给浏览器,恰好 html 规范中,有一个 ​picture 标签,这个标签配合 ​source 和 ​img 标签,可以完美地解决这个问题,如下:

<picture>  
    <source srcset="logo.webp" type="image/webp">
    <img src="logo.png" alt="logo"> 
</picture>

浏览器当遇到这段代码时,会自动匹配 ​source 中的备选多媒体资源,尽可能地使用最恰当的那一个资源。

这里可能有一个问题,就是 ​picture 标签的兼容性问题,如下:

image.png

可以发现除了 ieopera mini 均支持,由于 ie 本身也不支持 ​webp 格式,所以我们可以忽略它。

在 create-react-app 中使用它

CRA 本身已经支持 ​webp 格式的图片,但是图片需要是静态的,即你首先应该有一个 ​webp 图片,如果是对于未来的图片,那没什么问题,但对于之前已经使用的图片,就必须手动一张一张转换,有点繁琐,有没有解决方案能够自动将之前的 ​jpg 或者 ​png 的图片转换为 ​webp 格式,或者在打包时,同时生成一个 ​webp 格式的副本呢? 答案是有的,可以使用 ​ImageminWebpWebpackPlugin 这个插件来完成这个工作,如下:

    new ImageminWebpWebpackPlugin({
      config: [
        {
          test: /\.(jpe?g|png)/,
          options: {
            quality: 75,
          },
        },
      ],
      overrideExtension: true,
      detailedLogs: false,
      silent: false,
      strict: true,
    })

在 CRA 中,可以通过 ​eject 或者 ​react-app-rewired 来覆盖 webpack 配置,我这里使用的是 customize-cra 这个库中的 addWebpackPlugin 方法。

该插件的默认的生成规则是,xxx.png 在打包时,同时会生成一个 ​xxx.webp 的副本,当然这个规则也可以在插件的配置中进行更改。

最后只需要把 img 元素简易封装一下即可,如下:

const WebpImage: React.FC<
  React.DetailedHTMLProps<
    React.ImgHTMLAttributes<HTMLImageElement>,
    HTMLImageElement
  >
> = props => {
  const { src } = props;

  const webpSrc = React.useMemo(() => {
    const nameChunks = src.split('.');
    nameChunks.pop();
    nameChunks.push('webp');
    return nameChunks.join('.');
  }, [src]);

  return (
    <picture>
      <source srcSet={webpSrc} type="image/webp" />
      <img {...props} />
    </picture>
  );
};

这里的封装比较简单,但作为演示够用了,效果如下:

image.png

network 中的加载情况:

image.png

总结

我示例中的图片,源文件大小为 184kbwebp 副本文件大小为 22kb,如下图:
image.png

由于我这里是有损压缩,所以体积减少比例大概是 ~88%,无损压缩的话,会比这个低一些。

参考

https://developers.google.com/speed/webp/
https://github.com/DonRai/react-image-webp
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture

狮乐园
日就月将,学有缉熙于光明

华丽的跌倒胜过无谓的徘徊

7.8k 声望
1.5k 粉丝
0 条评论
推荐阅读
手把手教你写一份优质的前端技术简历
不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。如果一份简...

tonychen153阅读 17.9k评论 5

封面图
正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青57阅读 8.6k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy49阅读 7.3k评论 12

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 7k评论 12

封面图
CSS 绘制一只思否猫
欢迎关注我的公众号:前端侦探练习 CSS 有一个比较有趣的方式,就是发挥想象,绘制各式各样的图案,比如来绘制一只思否猫?思否猫,SegmentFault 思否的吉祥物,是一只独一无二、特立独行、热爱自由的(&gt;^ω^&lt...

XboxYan48阅读 3.3k评论 14

封面图
「多图预警」完美实现一个@功能
一天产品大大向 boss 汇报完研发成果和产品业绩产出,若有所思的走出来,劲直向我走过来,嘴角微微上扬。产品大大:boss 对我们的研发成果挺满意的,balabala...(内心 OS:不听,讲重点)产品大大:咱们的客服 I...

wuwhs32阅读 3.5k评论 5

封面图
还在用 JS 做节流吗?CSS 也可以防止按钮重复点击
举个例子:一个保存按钮,为了避免重复提交或者服务器考虑,往往需要对点击行为做一定的限制,比如只允许每300ms提交一次,这时候我想大部分同学都会到网上直接拷贝一段throttle函数,或者直接引用lodash工具库

XboxYan35阅读 2.7k评论 2

封面图

华丽的跌倒胜过无谓的徘徊

7.8k 声望
1.5k 粉丝
宣传栏