乐趣区

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

WebP 介绍

WebP 是 Google 推出的一种同时提供了有损和无损两种压缩形式的图片格式,劣势体现在其优良的图像压缩算法,可能带来更小的图片体积,同时领有更高的的图像品质。依据官网阐明,WebP 在无损压缩的状况下能比 PNG 缩小 26% 的体积,有损压缩的状况能比 JPEG 缩小 25%-34% 的体积。

下图能够看出,绝对于传统的图片格式,WebP 格局存在浏览器兼容性方面的问题。本文通过工程化的伎俩来实现 WebP 格局的自适应加载。

传统做法

为了在前端我的项目里用上 WebP 格局,并且兼容不反对该格局的浏览器,通常的做法是判断浏览器支持性,引入 WebP 图片或其余通用格局的图片。针对 HTML、JS、CSS 三种引入图片的场景,有以下几种解决形式:

HTML

借助 <picture> 标签自适应加载的个性,如下,WebP 格局放在 <source> 标签,用 JPEG、PNG 等通用格局做兜底。

<picture>

  <source srcSet="https://p3-imagex.byteimg.com/imagex-rc/preview.jpg~tplv-19tz3ytenx-147.webp" type="image/webp" />

  <img decoding="async" loading="lazy" src="https://p3-imagex.byteimg.com/imagex-rc/preview.jpg~tplv-19tz3ytenx-147.jpeg" />

</picture>

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 的兼容性,所以须要有一个全局的标记。通过在 <head> 标签里注入判断代码,若浏览器反对 WebP 格局则在根节点增加 ”webp” 类名标识,能够在页面渲染前获取浏览器对 WebP 格局的兼容性。

前端我的项目通常会用到 html-webpack-plugin,该插件能够帮助创立 HTML 文件并主动引入 Webpack 生成的 bundle,插件中提供有多个 hook,如下图所示。为确保曾经生成了 head 和 body 标签,能够抉择 在 alterAssetTagGroup 阶段注入相干的判断代码。

替换图片

接下来则依据全局标识将图片替换为相应的 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 里还须要对 <link> 引入的 CSS 文件做解决,将 <link> 转为 <script> 标签,并注入代码获取浏览器的 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 = '${url.replace(/.css/,'.webp.css')}';

            } else {style.href = '${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 格局,则须要接入图片解码 SDK。image-observer-mini 是 veImageX 官网提供的 SDK,该 SDK 能够判断浏览器对 WebP 格局的兼容性,优先采纳浏览器硬解的形式,对于不反对该格局的浏览器则采纳软解 + canvas 绘制的模式。


  • 1 元 100GB 流量包,限量抢购!资源包下单地址:https://console.volcengine.co…
退出移动版