Skip to content

webp是该用起来了

Posted on:May 22, 2020

webp 的好处不用多说了,相比 jpg 和 png 格式,文件大小能节省很多。但因为并不是所有浏览器都支持,所以需要做一些兼容处理。

得到 webp

随便搜索引擎一搜,会有很多在线转换图片格式的工具,也有很多可供下载的工具,看你自己怎么用了,比如说前端的gulp,会有对应的图片压缩转换的插件可使用。这里我就说下 google 提供的的命令行工具webp

在 Mac 下可以使用 homebrew 安装 webp 工具:

brew install webp

安装完成之后,就可以使用 cwebp 将 jpg 或 png 转成 webp 格式:

cwebp [-preset <...>] [options] in_file [-o out_file]

更多的操作自行查阅文档吧。

后端处理

这里就不细说了,大概原理就是在浏览器向服务器发起请求时,对于支持 webp 图片的浏览器,会在请求头 Accept 中带上 image/webp 的信息,服务器就能识别浏览器是否支持 webp 从而返回对应的图片格式,具体可查看凹凸实验室的文章——探究 WebP 一些事儿

前端处理

在页面上可以使用picture标签来做兼容,会根据浏览器设备版本选择不同的选项:

<picture>
  <!-- JPEG -->
  <source srcset="./images/example.jpg" type="image/jpeg" />
  <!-- WebP -->
  <source srcset="./images/example.webp" type="image/webp" />
  <!-- The fallback -->
  <img src="./images/example.jpg" alt="example" />
</picture>

但这样写会比较臃肿,可以通过Service Wroker来拦截网络请求,监听 fetch 事件,根据 Accept 头部来判断浏览器是否支持 webp 图片,并且查找是否存在image/webpMime 类型,这样就可以确定并替换成 webp 返回。

// serviceWorker.ts
window.addEventListener("fetch", (e: any) => {
  // Clone the request
  const req = e.request.clone();

  // Check if the image is a jpeg
  if (/\.jpg$|.png$/.test(e.request.url)) {
    // Get all of the headers
    const headers: string[] = Array.from(req.headers.entries());

    // Inspect the accept header for WebP support
    const acceptHeader: string[] = headers.filter(item => item[0] === "accept");
    const supportsWebp = acceptHeader[1].includes("webp");

    // If we support WebP
    if (supportsWebp) {
      // Build the return URL
      const returnUrl = req.url.substr(0, req.url.lastIndexOf(".")) + ".webp";

      e.respondWith(
        fetch(returnUrl, {
          mode: "no-cors",
        })
      );
    }
  }
});

然后页面就可以写得简单些:

<picture>
  <source srcset="./images/example.jpg" type="image/jpeg" />
</picture>

但看了下这个例子,也一样会去请求 jpg 的图片,那这样就不起作用了。

React 下使用且要支持 CSS 内使用

上面的方式是挺方便的,但因为我的个人网站目前使用 react,有些图片是导入进去使用的,我试了下如此 service worker 的方式就不支持了。加之很多时候会在 css 内引入背景图片,所以不能在 service worker 做判断,那就还是在页面上操作:

const ImgWithFallback = ({
  src,
  fallback,
  type = "image/webp",
  ...delegated
}) => {
  return (
    <picture>
      <source srcSet={src} type={type} />
      <img src={fallback} {...delegated} />
    </picture>
  );
};

使用的时候是:

<ImgWithFallback
  src={require("./images/example.webp")}
  fallback={require("./images/example.png")}
  alt="example"
/>

想要在 css 内使用 webp,就得知道浏览器是否支持 webp,然后在 html 加上一个 class 做标识,如此在 css 内使用 background 引入图片的时候,就可以根据 class 去引入不同格式的图片。

如何知道浏览器是否支持 webp 呢,创建一个 canvas 判断下:

function canUseWebP() {
  var elem = document.createElement("canvas");
  if (!!(elem.getContext && elem.getContext("2d"))) {
    // was able or not to get WebP representation
    return elem.toDataURL("image/webp").indexOf("data:image/webp") == 0;
  }
  // very old browser like IE 8, canvas not supported
  return false;
}

然后根据判断在 html 标签加上 class 即可。

参考: