前端开发:WebP自适应提高开发性能

WebP介绍 WebP 是 Google 推出的一种同时提供了有损和无损两种压缩方式的图片格式,优势体现在其优秀的图像压缩算法,能够带来更小的图片体积,同时拥有更高的的图像质量。根据官方说明,WebP 在无损压缩的情况下能比 PNG 减少26%的体积,有损压缩的情况能比 JPEG 减少25%-34%的体积。
下图可以看出,相对于传统的图片格式,WebP 格式存在浏览器兼容性方面的问题。本文通过工程化的手段来实现 WebP 格式的自适应加载。
前端开发:WebP自适应提高开发性能
文章图片

传统做法 为了在前端项目里用上 WebP 格式,并且兼容不支持该格式的浏览器,通常的做法是判断浏览器支持性,引入 WebP 图片或其他通用格式的图片。针对HTML、JS、CSS 三种引入图片的场景,有以下几种处理方式:
HTML 借助 标签自适应加载的特性,如下,WebP 格式放在标签,用JPEG、PNG等通用格式做兜底。

前端开发:WebP自适应提高开发性能
文章图片

JS 通过 JS 判断浏览器是否支持 WebP,若支持则引入 WebP 格式的图片,若不支持则引入JPEG、PNG等通用格式。有以下两种判断方式:
  1. canvas 判断
isSupportWebp = document.createElement("canvas").toDataURL("image/webp").indexOf("data:image/webp") === 0;

  1. 加载 WebP 图片判断
function isSupportWebp(callback) {var img = new Image(); img.onload = function () {var result = (img.width > 0) && (img.height > 0); callback(result); }; img.onerror = function () {callback(false); }; img.src = 'data:image/webp; base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; }

CSS 首先需要判断浏览器是否支持 WebP 格式,若支持则在 HTML 根节点添加类名标识。
document.documentElement.classList.add('webp')

然后利用选择器的优先级做到 WebP 的自适应加载,CSS 中引入图片的方式做如下改动:
.img { background-image: url('https://p3-imagex.byteimg.com/imagex-rc/preview.jpg~tplv-19tz3ytenx-147.jpeg') }.webp .img { background-image: url('https://p3-imagex.byteimg.com/imagex-rc/preview.jpg~tplv-19tz3ytenx-147.webp') }

自动化处理 当项目中有大量本地图片引入时,手动处理的方式就显得比较繁琐,除了要根据图片引入的方式分别处理,还需要事先将所有图片转为 WebP 格式。
考虑到前端项目大多由 Webpack 构建,因此,尝试开发一款 Webpack 插件支持将项目里的图片转为 WebP 格式并且支持图片格式自适应。
方案设计 首先需要将项目中的图片转为 WebP 格式,考虑到本地转换的耗时较大,这一步更适合放到云端来做,在云端处理图片的服务也相对成熟,大多数云服务厂商均有提供,且图片上传之后也可以显著减少打包产物的体积。
上传图片
第一步是收集项目中的图片文件上传至云端,file-loader 支持将 import/require() 引入的文件写入到目标文件夹并将文件解析为 url,所以可在 file-loader 的基础上进行改造,方案是:
  1. 获取 loader 匹配到的图片文件,将图片上传至云端,在云端生成 WebP 格式的图片;
  2. 将原始图片文件替换为图片服务生成的URL;
  3. 若图片上传失败则降级为 file-loader 的处理流程,将图片传入输出文件夹。
插入标记
处理过程中有多个地方依赖浏览器对 WebP 的兼容性,所以需要有一个全局的标记。通过在标签里注入判断代码,若浏览器支持 WebP 格式则在根节点添加"webp"类名标识,可以在页面渲染前获取浏览器对 WebP 格式的兼容性。
前端项目通常会用到 html-webpack-plugin,该插件可以协助创建 HTML 文件并自动引入 Webpack 生成的 bundle,插件中提供有多个 hook,如下图所示。为确保已经生成了 head 和 body 标签,可以选择 在 alterAssetTagGroup 阶段注入相关的判断代码。
前端开发:WebP自适应提高开发性能
文章图片

替换图片
接下来则根据全局标识将图片替换为相应的 url。根据图片被引入的位置,分为以下两种情况:
  1. 图片在 JS 中被引入。将图片模块替换为一段 JS 代码,根据类名标识返回 WebP 或者通用格式的图片 url ;
  2. 图片在 CSS 中被引入。若在 CSS 模块中引入 JS 代码,一方面会执行出错,另一方面 css-loader 会在编译阶段执行这段代码,达不到在浏览器端判断 WebP 兼容性的目的,因此 CSS 部分需要单独处理。
处理CSS
处理CSS中引入的图片有两种方案:
  1. 利用 CSS 选择器优先级。在 CSS 中有图片引入的类后边插入带 webp 类选择器的样式,原理同手动替换时处理 CSS 的方式。
  2. 全量生成 WebP 版 CSS。即文件中引入的图片皆为 WebP 格式,在链接样式文件时判断根节点类名标识,引入 WebP 版 CSS 或原始 CSS。
第一种方案的缺点是改变了部分样式的优先级,可能会影响整体样式,因此采用第二种方案。
方案实现 本文的方案选择了火山引擎提供的 veImageX 图片服务来处理图片。veImageX 是火山引擎提供的图片整体解决方案,能够将图片转换为 WebP、HEIF、AVIF等多种格式 ,支持从图片上传、存储、处理到分发的完整流程。
图片上传到 veImageX 之后,可以快速接入 veImageX 的各项云端处理能力,如:裁剪、旋转、滤镜以及橡皮擦、内容擦除等多项 AI 处理能力,并且可以方便地通过更换 URL 后缀的方式获取不同格式的图片。因此,以下方案实现基于 veImageX 展开。
  1. 接入 veImageX 图片服务,获取 accessKey、secretKey 以及服务ID,具体接入方式请参考说明文档;
  2. Webpack 插件分为两部分,在 loader 里上传并替换图片,在 plugin 里生成 webp 类名标记并处理 CSS 中引入的图片。通过 Webpack loader 获取项目里的图片文件,借助火山引擎提供的 SDK 将图片上传至 veImageX,并将图片模块替换为服务生成的 url。基于 veImageX 改变 URL 后缀获取相应图片格式的特性,根据图片被引入的位置做不同的处理。
如下,JS 中引入的图片根据浏览器兼容性来判断,CSS 中引入的图片则返回通用格式。
result = `var ret = ''; if (typeof document === 'object') {var format = ''; document.documentElement.classList.forEach(item => { if (item.match(/__(\w+)__/)) format = (item.match(/__(\w+)__/))[1]})if (format) {ret = "//${formatDomain}${imagexUri}~${options.template}${urlParams}." + format; } else {ret = "//${formatDomain}${imagexUri}~${options.template}${urlParams}.image"; }} else {ret = "//${formatDomain}${imagexUri}~${options.template}${urlParams}.image"; }${esModule ? 'export default' : 'module.exports ='} ret`;

该部分单独封装成了 veimagex-webpack-loader,支持将项目中的图片上传至 veImageX,不需要 WebP 自适应能力的可直接使用该loader。
  1. 在 html-webpack-plugin 的 alterAssetTagGroup hook里插入浏览器 WebP 兼容性判断的代码,这部分代码在浏览器端执行,若浏览器支持 WebP 则在根节点添加"__webp__"类名标识,如下:
compiler.hooks.compilation.tap('ImagexWebpackPlugin', function (compilation) {const hooks = self.htmlWebpackPlugin.getHooks(compilation); hooks.alterAssetTagGroups.tapAsync('ImagexWebpackPlugin',self.checkSupportWebp.bind(self)); }); ImagexWebpackPlugin.prototype.checkSupportFormat = function (htmlPluginData,callback) {htmlPluginData.headTags.unshift({tagName: 'script',closeTag: true,attributes: {type: 'text/javascript'},innerHTML: `var isSupportFormat = !![].map && document.createElement('canvas').toDataURL('image/${this.options.format}').indexOf('data:image/${this.options.format}') == 0; if (isSupportFormat) document.documentElement.classList.add('__${this.options.format}__'); `}); callback(null, htmlPluginData); };

  1. 对于 CSS 文件的处理是全量生成 WebP 版 CSS 的方案,所以在 alterAssetTagGroup hook里还需要对 引入的 CSS 文件做处理,将 转为 标签,并注入代码获取浏览器的 WebP 兼容性,若支持 WebP 则添加对 WebP 版 CSS 文件的引用,如下:
htmlPluginData.headTags.forEach((tag, index) => {if (tag.tagName === 'link' && tag.attributes.rel === 'stylesheet') {const url = tag.attributes.href; htmlPluginData.headTags[index] = {tagName: 'script',closeTag: true,attributes: {type: 'text/javascript'},innerHTML: `var head = document.querySelector('head'); var style = document.createElement('link'); style.rel = "stylesheet"; if (document.documentElement.classList.contains('__webp__')) {style.href = 'https://www.it610.com/article/${url.replace(/.css/,'.webp.css')}'; } else {style.href = 'https://www.it610.com/article/${url}'; }head.appendChild(style)`}; }})

  1. WebP 版 CSS 文件的生成在 Webpack 编译完成之后,在 done 这个 hook 里边可以拿到 Webpack 编译之后的产物,遍历 CSS 文件,替换文件中引入的 veImageX URL,为每个 CSS 文件生成其 WebP 版本,如下:
function handleCss(dir, options) {const files = fs.readdirSync(dir); files.forEach(function (file) {const filePath = `${dir}/${file}`; const info = fs.statSync(filePath); if (info.isDirectory()) {handleCss(filePath, options); } else {if (file.match(/.css$/) && !file.match(new RegExp(`\.${options.format}\.css`))) {let result = fs.readFileSync(filePath, 'utf-8'); const reg = new RegExp('\/\/.+\.image', 'g'); if (result.match(reg)) {const urls = Array.from(new Set(result.match(reg))); urls.map(url => {const urlReg = new RegExp(`${url}`, 'g'); result = result.replace(urlReg, `${url.slice(0, url.length - 5)}${options.format}`); })}fs.writeFileSync(path.join(dir, file.replace(/.css/, `.${options.format}.css`)), result, 'utf8'); }}}); }

该方案最终产出为一个 Webpack 插件 veimagex-webpack-plugin,该插件不仅支持 WebP 格式,还支持 AVIF 和 HEIF 格式的自适应引入。
其他方案 CDN方案 为了加速访问,图片文件通常会被上传到CDN。实现图片格式自适应最好的方案是在 CDN 侧支持,CDN 根据请求头中携带的 Accept 字段判断浏览器兼容情况,下发最适合当前环境的图片格式,只需引入一个 url 就能实现图片格式自适应下发,不用再做复杂的判断。veImageX 现已支持了 WebP、HEIF、AVIF 的自适应分发,可以体验一下。
解码SDK 【前端开发:WebP自适应提高开发性能】若需要在所有的浏览器中支持 WebP 格式,则需要接入图片解码SDK。image-observer-mini 是 veImageX 官方提供的 SDK,该 SDK 可以判断浏览器对 WebP 格式的兼容性,优先采用浏览器硬解的方式,对于不支持该格式的浏览器则采用软解 + canvas 绘制的形式。
  • 1元 100GB 流量包,限量抢购!资源包下单地址:https://console.volcengine.co...

    推荐阅读