前端:WebP自适应实践
WebP介绍
WebP 是 Google 推出的一种同时提供了有损和无损两种压缩方式的图片格式,优势体现在其优秀的图像压缩算法,能够带来更小的图片体积,同时拥有更高的的图像质量。根据官方说明,WebP 在无损压缩的情况下能比 PNG 减少26%的体积,有损压缩的情况能比 JPEG 减少25%-34%的体积。
下图可以看出,相对于传统的图片格式,WebP 格式存在浏览器兼容性方面的问题。本文通过工程化的手段来实现 WebP 格式的自适应加载。
传统做法
为了在前端项目里用上 WebP 格式,并且兼容不支持该格式的浏览器,通常的做法是判断浏览器支持性,引入 WebP 图片或其他通用格式的图片。针对HTML、JS、CSS 三种引入图片的场景,有以下几种处理方式:
HTML
借助 标签自适应加载的特性,如下,WebP 格式放在 标签,用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等通用格式。有以下两种判断方式:
- canvas 判断
isSupportWebp = document.createElement("canvas").toDataURL("image/webp").indexOf("data:image/webp") === 0;
- 加载 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 的基础上进行改造,方案是:
- 获取 loader 匹配到的图片文件,将图片上传至云端,在云端生成 WebP 格式的图片;
- 将原始图片文件替换为图片服务生成的URL;
- 若图片上传失败则降级为 file-loader 的处理流程,将图片传入输出文件夹。
插入标记
处理过程中有多个地方依赖浏览器对 WebP 的兼容性,所以需要有一个全局的标记。通过在 标签里注入判断代码,若浏览器支持 WebP 格式则在根节点添加"webp"类名标识,可以在页面渲染前获取浏览器对 WebP 格式的兼容性。
前端项目通常会用到 html-webpack-plugin,该插件可以协助创建 HTML 文件并自动引入 Webpack 生成的 bundle,插件中提供有多个 hook,如下图所示。为确保已经生成了 head 和 body 标签,可以选择 在 alterAssetTagGroup 阶段注入相关的判断代码。
替换图片
接下来则根据全局标识将图片替换为相应的 url。根据图片被引入的位置,分为以下两种情况:
- 图片在 JS 中被引入。将图片模块替换为一段 JS 代码,根据类名标识返回 WebP 或者通用格式的图片 url ;
- 图片在 CSS 中被引入。若在 CSS 模块中引入 JS 代码,一方面会执行出错,另一方面 css-loader 会在编译阶段执行这段代码,达不到在浏览器端判断 WebP 兼容性的目的,因此 CSS 部分需要单独处理。
处理CSS
处理CSS中引入的图片有两种方案:
- 利用 CSS 选择器优先级。在 CSS 中有图片引入的类后边插入带 webp 类选择器的样式,原理同手动替换时处理 CSS 的方式。
- 全量生成 WebP 版 CSS。即文件中引入的图片皆为 WebP 格式,在链接样式文件时判断根节点类名标识,引入 WebP 版 CSS 或原始 CSS。
第一种方案的缺点是改变了部分样式的优先级,可能会影响整体样式,因此采用第二种方案。
方案实现
本文的方案选择了火山引擎提供的 veImageX 图片服务来处理图片。veImageX 是火山引擎提供的图片整体解决方案,能够将图片转换为 WebP、HEIF、AVIF等多种格式 ,支持从图片上传、存储、处理到分发的完整流程。
图片上传到 veImageX 之后,可以快速接入 veImageX 的各项云端处理能力,如:裁剪、旋转、滤镜以及橡皮擦、内容擦除等多项 AI 处理能力,并且可以方便地通过更换 URL 后缀的方式获取不同格式的图片。因此,以下方案实现基于 veImageX 展开。
- 接入 veImageX 图片服务,获取 accessKey、secretKey 以及服务ID,具体接入方式请参考说明文档;
- 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。
- 在 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);
};
- 对于 CSS 文件的处理是全量生成 WebP 版 CSS 的方案,所以在 alterAssetTagGroup hook里还需要对 引入的 CSS 文件做处理,将 转为
前端:WebP自适应实践的更多相关文章
- web前端开发最佳实践笔记
一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...
- Web前端优化最佳实践及工具集锦
Web前端优化最佳实践及工具集锦 发表于2013-09-23 19:47| 21315次阅读| 来源Googe & Yahoo| 118 条评论| 作者王果 编译 Web优化Google雅虎P ...
- [转] Web 前端优化最佳实践之 Mobile(iPhone) 篇
原文链接:http://dbanotes.net/web/best_practices_for_speeding_up_your_web_site_server_mobile.html Web 前端优 ...
- 如何通过 WebP 自适应方案减少图片资源大小
前言 我们知道,理想的网页应该在 1 秒内打开,而在页面的整体大小中,图片往往是所占比例最大的一部分(大约占到 60% 以上,更多了解请点击),也可以参照如下图所示.优化图片不仅可以加快页面显示,还能 ...
- 【社区公益】送《Web前端开发最佳实践》给需要的人
算起来至今,我进入软件开发行业已经有11年之久.从最初的研究人工智能,到后来的Web开发,控件开发,直到现在纯粹的Web前端开发.虽然没有大的作品问世,但也是勤勤恳恳,踏实做事,低调做人.从来不吹牛逼 ...
- Web前端开发最佳实践系列文章汇总
Web前端开发最佳实践(1):前端开发概述 Web前端开发最佳实践(2):前端代码重构 Web前端开发最佳实践(3):前端代码和资源的压缩与合并 Web前端开发最佳实践(4):在页面中添加必要的met ...
- Web前端开发最佳实践(9):CSS代码太太乱,重复代码太多?你需要精简CSS代码
前言 提高网站整体加载速度的一个重要手段就是提高代码文件的网络传输速度.之前提到过,所有的代码文件都应该是经过压缩了的,这可提高网络传输速度,提高性能.除了压缩代码之外,精简代码也是一种减小代码文件大 ...
- Web前端开发最佳实践(8):还没有给CSS样式排序?其实你可以更专业一些
前言 CSS样式排序是指按照一定的规则排列CSS样式属性的定义,排序并不会影响CSS样式的功能和性能,只是让代码看起来更加整洁.CSS代码的逻辑性并不强,一般的开发者写CSS样式也很随意,所以如果不借 ...
- Web前端开发最佳实践(7):使用合理的技术方案来构建小图标
大家都对网站上使用的小图标肯定都不陌生,这些小图标作为网站内容的点缀,增加了网站的美观度,提高了用户体验,可是你有没有看过在这些网站中使用的图标都是用什么技术实现的?虽然大部分网站还是使用普通的图片实 ...
随机推荐
- Python中字符串常用方法
capitalize() String.capitalize() 将字符串首字母变为大写 name = 'xiaoming' new_name = name.capitalize() print(ne ...
- k8s replicaset controller 分析(3)-expectations 机制分析
replicaset controller分析 replicaset controller简介 replicaset controller是kube-controller-manager组件中众多控制 ...
- [Beta]the Agiles Scrum Meeting 2
会议时间:2020.5.11 20:00 1.每个人的工作 今天已完成的工作 成员 已完成的工作 yjy 修复bug将自动评测改为异步HTTP请求 tq 实现查看.删除测试点功能的后端将自动评测改为异 ...
- 21.10.9 test
T1 购票方案 \(\color{green}{100}\) 对于每个时间节点维护它作为每种票所能包含的最后一个点时,这种票的起始点位置,由于这个位置是单调的,所以类似双指针维护,\(O(KN)\) ...
- Spring IOC(控制反转)和DI(依赖注入)原理
一.Spring IoC容器和bean简介 Spring Framework实现了控制反转(IoC)原理,IoC也称为依赖注入(DI). 这是一个过程,通过这个过程,对象定义它们的依赖关系,即它们使用 ...
- 设计模式学习-使用go实现建造者模式
建造者模式 定义 适用范围 与工厂模式的区别 优点 缺点 参考 建造者模式 定义 Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式. 建造者模式(Builder Patt ...
- 【完美解决】IDEA 中 Maven 报错 Cannot resolve xxx 和 Maven 中 Dependencies 报红/报错。
目录 前提 场景 解决办法 1.首先,清除缓存,点击之后重启IDEA. 2.关闭IDEA,打开项目文件夹 3.重新打开 IDEA,找到右边的 Maven 4.解决 Maven 中 Dependenci ...
- 【Python+postman接口自动化测试】(6)Chrome开发者工具
Chrome开发者工具 Elements: HTML元素面板,用于定位查看元素源代码 Console: js控制台面板,js命令行,查看前端日志 Sources: 资源面板,用于断点调试js Netw ...
- Java8新特性Stream流应用示例
Java8新特性介绍 过滤集合 List<String> newList = list.stream().filter(item -> item != null).collect(C ...
- Cannot load module file xxx.iml的两种解决方法
一. 一种是点击左上角File,然后点击Invalidate Caches / Restart...,弹出对话框再点击Invalidate and Restart等待工程重新加载,问题就解决了. 二. ...