手摸手教你 360 度无死角 WebP 能力检测
WebP 作为当今性价比最高的图片格式之一, 广泛应用于 Web 应用, 无论是电商网站还是图片流网站, 都能看到它的身影. 但它仍不被某些浏览器所支持, 这篇文章通过三种方案, 帮你全方位扫清 WebP 能力检测.
什么是 WebP
WebP 是 Google 开发一种同时采用有损和无损压缩的图像格式,于 2010 年 9 月 30 日首次发布, 可用于网页上大量摄影, 半透明和图形图像. WebP 有损压缩的程度是可调的, 因此用户可以在文件大小和图像质量之间自由权衡. WebP 通常平均比 JPEG 和 JPEG 2000 多压缩 30%, 而且不会损失图像质量. 关于 WebP 格式的压缩实验, 可参考 WebP 相对于 PNG、JPG 有什么优势? - Hahn 的回答 - 知乎 的回答, 这篇文章不做赘述.
WebP 除了有损和无损形式, 它还支持 Alpha 通道(图像可能具有透明度), 也支持 Animation(即动图, 可类比 GIF). 目前 Youtube 在支持 WebP 格式的浏览器中, 将视频预览的图像格式全部转换为 WebP; 而不支持 WebP 的浏览器如 Safari, Youtube 干脆就取消了视频预览的特性.
WebP 已诞生 10 年之久, 并且对于图片性能的提升肉眼可见, 但部分浏览器仍不支持. 下面是 caniuse 给出的数据, 可见 IE 和苹果系的浏览器仍不支持, 这里可能是一些技术原因, 但更多是商业因素. 因此这篇文章教你 360° 无死角 WebP 的能力检测.
方案一
方案一通过 JavaScript 脚本进行检测, 这里用到了 Modernizr, 该库提供了大量浏览器能力检测的脚本. 我已经把 WebP 能力检测的脚本放到了 gist, 你可访问 WebP 能力检测.js 下载.
在浏览器中执行这段脚本, 最新版的 Chrome 浏览器给 html 标签增加了 webp
, webp-alpha
, webp-aniamtion
, webp-lossless
的类; 而 Safari 浏览器增加了 no-webp
的类, 因此我们在 css 中, 只要写出形如下的代码就可以了.
.no-webp .cover { background-image: url('image.jpg'); } .webp .cover { background-image: url('image.webp'); }
当然如果用到 img 标签上, 你还可以这么写.
const [supportWebP, setSupportWebP] = useState( document.documentElement.classList.contains('webp'), ) return <img src={supportWebP ? 'image.webp' : 'image.jpg'} alt="img" />
Modernizr 给出的脚本做了较多的兼容, minify 后也有 1.2kb 的大小, 下面这段代码比较精简, 根据实际情况可以考虑使用. 它返回一个 Boolean 类型, 你可以把结果存到 Store 或者 LocalStorage 中, 也可以跟 Modernizr 一样, 给 html 的 class 增加一个 webp 标识. 这段代码收集到了我的工具集 yancey-js-util, 有兴趣也可使用 yarn add yancey-js-util
安装使用.
export const checkWebp = () => {
return (
document
.createElement('canvas')
.toDataURL('image/webp')
.indexOf('data:image/webp') === 0
)
}
方案二
第二种方式是使用 HTML5 新增的 picture 标签. 与 audio, video 标签类似, 它们都可以包含 source 标签, 用于让浏览器自行选择最优的格式来呈现. 这种方案无需 js 代码, 性能会更好一些, 但它的缺点显而易见, 即无法应用于 css 的 background-image.
<picture> <source srcset="img/image.webp" type="image/webp" /> <source srcset="img/image.jpg" type="image/jpeg" /> <img src="img/image.jpg" alt="img" /> </picture>
方案三
方案一可以应用于 backgroud-image 和 img 标签两种场景, 似乎是一种万全之策, 但它毕竟依托于 JavaScript. 如果用户阻止浏览器使用 JS 脚本, 或者说在 SSR 服务端渲染阶段(因为这些检测脚本涉及到了 DOM 操作, 而服务端是无法操作 DOM 的), 方案一就不 ok 了.
第一种情况比较极端, 我们可以一开始就给 html 标记上 no-js
的 class, 当 DOM 加载完成后执行如下脚本, 如果 no-js 被移除了, 那证明 JS 没有被阻止; 否则降级处理, 代码如下所示.
<style>
.no-js .elementWithBackgroundImage {
background-image: url('image.jpg');
}
</style>
<script>
document.documentElement.classList.remove('no-js')
</script>
对于 SSR 场景下, 方案二可保证所有的 img 标签都能展示最优结果, 但对于 background-image 就无能为力了. 这种情况下, 我们依然可以借助后端之力. 在 HTTP 请求头中的 accept 字段中, 我们发现了 image/webp
的字样, 后端接收到这个请求, 只要判断存在 image/webp
, 就证明该浏览器支持 WebP 格式, 后端将你的浏览器支持 WebP 再告诉给前端, 那这个问题就迎刃而解了.
下面这段代码是我的博客中的真实案例, 项目使用了 Next.js 做服务端渲染, 通过 getServerSideProps
方法将 isSupportWebp
返回给前端, 进而前端判断是否有能力展示 WebP 格式的图片.
import React, { FC } from 'react'
import { GetServerSidePropsContext } from 'next'
import Layout from 'src/containers/Layout/Layout'
import HomeContainer from 'src/containers/Home/Home'
interface Props {
isSupportWebp: boolean
}
// 前端以 props 的形式获取该浏览器是否支持 WebP
const Index: FC<Props> = ({ isSupportWebp }) => {
return (
<Layout>
<HomeContainer isSupportWebp={isSupportWebp} />
</Layout>
)
}
// 后端判断该浏览器是否支持 WebP
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
return {
props: {
isSupportWebp: ctx.req.headers.accept?.includes('image/webp'),
},
}
}
export default Index
小结
Emmmm, 没啥可小结的, 还是希望各位浏览器大大们早日统一吧.
参考
PREVIOUS POST
聊一聊 ES6 的 Proxy 与 Reflect
NEXT POST
浅谈 Server-Sent Events