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/webp
Mime 类型,这样就可以确定并替换成 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 即可。
参考:
- https://aotu.io/notes/2015/11/06/webp-responsive-image/
- https://aotu.io/notes/2016/06/23/explore-something-of-webp/
- https://www.npmjs.com/package/react-image-webp
- https://joshwcomeau.com/performance/embracing-modern-image-formats/
- https://developers.google.com/speed/webp/docs/using
- https://css-tricks.com/using-webp-images/#article-header-id-3