1. webp相比png
无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之后,WebP 还是可以减少 28% 的文件大小。来一张图对比下。
png: xxx.png webp: xxx.webp
可以看到使用webp图片后,使用图片的体积下降了85/100左右
2. 接入方案
1.腾讯云提供的方案
腾讯云提供的两种解决方案。
第一种是对于域名开启请求头部自适应,在客户端请求发起的时候,根据请求头部的accept字段来支持。 举个例子,accept: image/png;image/webp;image/avif。服务端在收到请求后,会降级返回图片。先返回avif,再返回webp,最后支持png。 这种方案适合是无需客户端改造的。但是在一些场景下不适用,比如说已有的业务怎么验证、客户端如果错误携带返回头等参考桌面端macos的safari。
第二种是万象数据的解决方案,域名提供不同格式的图片,业务自行判断。比如url为 xxx.png。 业务需要webp,使用格式为xxx.png?imageMogr2/format/webp。这种方案有对业务有一定改造工作量,但是自由程度较高,且影响范围为自己的单一业务,不会影响到域名下的其他业务。
综合业务的类型,这里使用第二种。需要注意成本问题,万象数据调用内部价格为6块/1k次。需要cdn缓存支持到。
2. 业界方案
1. 使用一段内联的webp来加载和渲染。
function check_webp_feature(feature, callback) {
var kTestImages = {
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==",
animation: "UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA"
};
var img = new Image();
img.onload = function () {
var result = (img.width > 0) && (img.height > 0);
callback(feature, result);
};
img.onerror = function () {
callback(feature, false);
};
img.src = "data:image/webp;base64," + kTestImages[feature];
}
这里可以看到是去下载和正常渲染webp。如果ok就认为是支持webp的。但是对于背景图,目前只有替换class的办法,对于新业务和已有业务的开发是很繁琐的。改动也是很容易出错的。
3. 我们的前端业务改造方案
1. 通过caniuse查询出webp的支持度如下,浏览器支持度为95/100左右。现代浏览器都几乎支持,除了safari对于这一块的支持度较晚,桌面平台为16开始支持,移动端为14开始支持。
这里我们采取的方案借鉴现代浏览器的特性,站在巨人的肩膀上,对webp做一个渐进的支持。
- 使用@support媒体查询对webp做渐进支持。我们只需要选择和webp支持相近,一定支持webp的css属性就好了。不支持媒体查询的不会用到webp图片,很安全。测试在ie浏览器桶都支持。
可以看到@supports对css的支持是很好的,比webp还高。
代码如下,我们选择了contain和 text-align-last属性。支持度为chrome53 edge79 safari16 firfox69 opera34,是webp的子集,可以放心使用。 对于移动端的ios设备,希望支持到14左右。这里选用了translate属性和-webit-touch-callout属性支持到14.5。通过联合查询可以看到对css的支持度达到92/100和93/100,95/100 和 90+ 差距不大。
// !!!url不能随意修改,会产生费用
@mixin webpimgurl($url) {
$webp: $url + '?imageMogr2/format/webp/ignore-error/1';
$png: $url;
background-image: url(#{$png});
// 大多数浏览器和ios16的safari
@supports (contain: none) and (text-align-last: auto) {
background-image: url('#{$webp}');
}
// safari 移动端14.5起支持
@supports (translate: 10px) and (-webkit-touch-callout: none) {
background-image: url('#{$webp}');
}
}
3. 使用CSS.supports来判断就好,原理和兼容度同CSS的媒体查询。
// opera
var supports = window.supportsCSS;
// not ie
if (window.CSS && window.CSS.supports) {
supports = window.CSS.supports;
}
if (supports) {
var supportContain = supports('contain', 'none');
var supportText = supports('text-align-last', 'auto');
var supportTranslate = supports('translate', '10px');
var supportTouchCallout = supports('-webkit-touch-callout', 'none');
var supportModeCSS = supportContain && supportText;
var supportModeIOS = supportTranslate && supportTouchCallout;
window.supportWebp = supportModeCSS || supportModeIOS;
console.log('supportWebp2', supportModeCSS, supportModeIOS, window.supportWebp);
}
4. 对于小程序的webp支持。由于小程序对版本获取很方便,这里使用对应接口判断版本即可。对于image的webp属性,是微信小程序自带的解码器,会对webp图片在低版本时处理。
export function supportWebp() {
if (!wx.getDeviceInfo || !wx.getAppBaseInfo) {
return false;
}
const data = wx.getDeviceInfo?.();
const sdkData = wx.getAppBaseInfo?.();
wx.log.info('getSystemInfoSync', data);
const system = data.system?.toLocaleLowerCase?.();
const sdk = sdkData.SDKVersion?.toLocaleLowerCase();
const { platform } = data;
wx.log.info('supportWebp', platform?.toLowerCase?.());
if (platform?.trim?.().toLowerCase?.() === 'devtools') {
return true;
}
// ios系统16有bug,暂时设置为false
if (platform?.trim?.().toLowerCase?.() === 'mac') {
return false;
}
if (platform?.trim?.().toLowerCase?.() === 'windows') {
return true;
}
if (system?.indexOf('ios') !== -1) {
const version = system.toLocaleLowerCase?.()?.replace?.('ios', '').trim?.();
const versionArray = version?.split('.');
let iosVersionMatch = false;
if (versionArray.length) {
wx.log.info('versionArray', versionArray);
const mainVersion = versionArray[0];
const mainVersionNumber = parseInt(mainVersion);
iosVersionMatch = mainVersionNumber >= 14;
}
wx.log.info('iosVersionMatch', iosVersionMatch);
if (iosVersionMatch) {
wx.report?.('app_webp_support', {
support: 1,
platform: 2,
});
return true;
}
wx.report?.('app_webp_support', {
support: 0,
platform: 2,
});
return false;
} else {
if (system?.indexOf('android') !== -1) {
wx.report?.('app_webp_support', {
support: 1,
platform: 1,
});
return true;
}
}
wx.report?.('app_webp_support', {
support: 0,
platform: 3,
});
return false;
}
在wxml中对图片使用wxs作为过滤器即可。web_support是全局注入的变量。
<image class="item-img"
src="{{ webpTools.parseImg(webp_support, 'xxxxxxx.png') }}">
这里的过滤器对图片处理需要限制域名和对图片后缀限制png/jpg/jpeg等情况处理。
function getUrlBase(url) {
// 使用正则表达式提取协议、域名和路径
var protocol = url.substring(0, url.indexOf("://"));
// 获取域名部分
var domainStartIndex = url.indexOf("://") + 3;
var domainEndIndex = url.indexOf("/", domainStartIndex);
var domain = url.substring(domainStartIndex, domainEndIndex);
// 获取路径部分
var path = url.substring(domainEndIndex);
var newUrl = protocol + '://' + domain + path;
var length = url.length;
var fileExtName = url.substring(length-4);
var protocol = url.substring(0,5);
return {
newUrl: newUrl,
domain: domain,
fileExtName: fileExtName,
protocol: protocol,
};
}
function parseImg(supportWebp, url) {
if (!url) {
return url;
}
if (typeof url !== 'string') {
return url;
}
var newUrlIndex = url.indexOf('?');
if (newUrlIndex === -1) {
var data = getUrlBase(url);
var newUrl = data.newUrl;
var domain = data.domain;
var fileExtName = data.fileExtName;
var protocol = data.protocol;
console.log('parseImg fileExtName protocol', protocol);
if (protocol !== 'https') {
console.log('parseImg fileExtName protocol', protocol);
return url;
}
if (fileExtName !== '.jpg' && fileExtName !== '.JPG' && fileExtName !== '.png' && fileExtName !=='.PNG' && fileExtName !== '.jpeg' && fileExtName !== '.JPEG' ) {
console.log('parseImg no support', fileExtName);
return url;
}
if (domain !== 'xxx.com' && domain === 'xxxx.qq.com') {
console.log('parseImg no support', domain);
return url;
}
// !!!url不能随意修改,会产生费用
if (domain && supportWebp) {
return newUrl+ '?imageView2/format/webp/ignore-error/1';
}
console.log('parseImg not support', domain, supportWebp);
return url;
}
console.log('parseImg not support webp', url);
// 有query不处理
return url;
}
module.exports = {
parseImg: parseImg,
};