WebP是Google推出的图片格式,同等画质下体积通常比JPEG小25%到35%,对首屏加载速度的提升非常明显。但一个现实问题是:浏览器支持度并不完全统一,尤其是一些老旧的移动端内置浏览器、企业内网环境中长期不更新的浏览器,对WebP的支持并不靠谱。如果在HTML里直接写死.webp地址,这些环境下图片就会直接裂开。比较稳妥的做法是先检测能力,再决定加载哪一版图片,而jQuery正好提供了方便的DOM遍历能力来完成这件事。

为什么不能只靠User-Agent判断
有些实现喜欢通过navigator.userAgent去嗅探浏览器版本来推断是否支持WebP,比如检测到Chrome 32以上就认为支持。这种方案的问题在于,UserAgent字符串可以被随意伪装,而且一些国产浏览器的内核版本和标识版本经常对不上,判断结果不可靠。
真正可靠的方式是直接让浏览器去解码一张WebP图片。因为浏览器只会对自己真正支持的格式触发onload,不支持的格式会走到onerror。这个探测只需要一张极小的WebP测试图(几十字节),耗时几乎可以忽略。检测的本质是利用HTMLImageElement的解码能力做一次试探,结果由浏览器自己给出,不依赖任何版本号猜测。
用jQuery封装WebP检测函数
下面这段代码把检测封装成一个返回Promise的函数,内部创建一个临时的<img>元素,加载一段用DataURL表示的1x1像素WebP图片。这段DataURL是固定的,不需要额外的网络请求,也不会受CDN配置影响。
function checkWebPSupport() {
return new Promise(function (resolve) {
var img = new Image();
// 一张极小的 1x1 有损 WebP 测试图,以 dataURL 形式内嵌,无需网络请求
img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=';
img.onload = function () {
// 宽高能正确读出,说明解码成功
resolve(img.width === 1 && img.height === 1);
};
img.onerror = function () {
resolve(false);
};
});
}这里有个细节要注意:必须同时判断img.width === 1。某些老版本浏览器虽然会触发onload,但实际并没有解码出图像内容,宽高是0。加上尺寸校验可以避免误判。另外用resolve(false)而不是reject,是为了让调用方不需要额外写catch分支,代码更整洁。
如果你的项目还在用较老的jQuery版本,Promise可以换成回调函数形式,逻辑完全一致。顺便一提,现代浏览器还提供了caches对象配合FeaturePolicy的检测方式,但兼容性不如图片探测法,不建议在需要覆盖老设备的场景使用。
根据检测结果动态替换图片路径
检测只是第一步,关键是把结果用起来。推荐的HTML写法是把原图地址放在src里作为兜底,把WebP地址存到data-webp属性中。支持WebP时才替换,不支持时什么都不做,页面天然降级,不会出现裂图。
<img src="/images/banner.jpg" data-webp="/images/banner.webp" class="js-webp" alt="首页横幅">
接着在页面初始化时执行检测并批量替换。jQuery的选择器加each遍历写起来非常直接:
checkWebPSupport().then(function (supported) {
if (!supported) {
return; // 不支持就保持原图
}
$('img.js-webp').each(function () {
var $img = $(this);
var webpSrc = $img.data('webp');
if (webpSrc) {
$img.attr('src', webpSrc);
}
});
});这里把替换范围限定在带js-webp类的图片上,而不是全站无差别替换。好处是可控性强,你可以只对首屏大图、轮播图这类体积大的资源启用,小图标走CSS雪碧图或字体图标,没必要掺和进来。
还有一个容易被忽略的坑:动态替换src会导致浏览器重新发起请求。如果原图已经被下载了一部分,就产生了浪费。缓解办法有两种,一是把src写成一张极小的占位图,真实地址全部放在data属性里,检测完成后一次性赋值;二是结合懒加载插件,让图片在进入视口前根本不加载,检测完成后再交给懒加载逻辑处理。
懒加载场景与缓存优化
如果站点已经接入了jQuery懒加载插件,替换逻辑要调整一下。懒加载插件通常也是从data属性读真实地址,所以可以让WebP替换发生在懒加载触发之前,只替换data属性而不动src:
checkWebPSupport().then(function (supported) {
if (!supported) return;
$('img.js-lazy').each(function () {
var $img = $(this);
var original = $img.data('original');
if (original && original.match(/\.(jpe?g|png)$/i)) {
// 把懒加载用的地址直接替换成同名的 webp 文件
$img.attr('data-original', original.replace(/\.(jpe?g|png)$/i, '.webp'));
}
});
});最后两点建议。第一,检测结果是会话内不变的,可以用一个模块级变量缓存结果,避免多张图片各自触发检测。第二,如果图片走CDN,很多CDN本身就支持根据请求头Accept自动返回WebP版本,这种情况下客户端检测反而是多余的,先确认CDN能力再决定是否自己实现,能省不少维护成本。整体方案的思路就是探测、缓存结论、批量决策,同样的模式也适用于AVIF等更新格式的渐进式增强。