移动端 HTML5 项目使用 WebP 图片探讨
引言
WebP是Google开发的一种图片文件格式,同时支持图片的无损压缩和有损压缩。WebP最初的目标是为了减少文件大小,从而加快网络上的页面访问速度。在相同的图片质量下,无损压缩图片比PNG格式小26%,有损压缩图片比JPEG小25~34%1。
根据官方的数据,WebP格式有不小的优势——大概有26%的带宽节约。事实上WebP在2010年发布,至今已经接近7年。在2013年我从事手游研发时项目已经全线引入了WebP。如果项目对图片质量要求比较高、图片资源又比较大的话,还是值得引入的。但是HTML5项目2由于本身的一些特殊性,使用WebP图片还是有不少的问题。
另外一点,出于政治(简单来说就是 Google 喜欢推行自己的技术标准,但是 Mozilla 等不一定全盘接受)和WebP图片本身缺陷的原因,未来 FireFox/Edge/Safari 等浏览器对于 WebP 的支持也没有完全确定。
图片格式对比
根据Google官方介绍的数据,WebP平均比PNG图片小26%。在讨论方案前,我挑选了以前项目中用到的60张PNG图片做了对比测试(由于大部分项目使用的图片都是带透明通道的PNG图片,JPEG相对较少,所以只对比了PNG图片)。
PNG图片压缩
PNG图片的压缩分为无损压缩和有损压缩两种,无损压缩一般是用ImageOptim去除图片中的冗余信息(大概可以压缩5%左右);有损压缩一般是从PNG24格式压缩为PNG8格式,pngquant是一个比较流行的库,可以使用image-webpack-loader每次打包时自动处理,也可以使用ImageAlpha一次批量处理好放入项目中。
实践中压缩PNG图片的比较好的方法是pngquant + ImageOptim,流程大致如下:
Every Bit Counts.
为了公平对比,使用WebP无损压缩对比PNG无损压缩,WebP有损压缩对比PNG8有损压缩。对于用户访问网络页面来说,Every Bit Counts,很多时候我们会牺牲一些色彩度来大幅度压缩文件(实际上,HTML5项目中很多图片都是处理成PNG8来大幅度提高页面的访问速度),所以PNG8的参考也是必要的——毕竟引入新技术要比生产环境有优势。
WebP vs PNG24 vs PNG8
为了验证官方数据,我找了一些资源做测试,数据生成方式:
- 原始图片文件:UI提供,大部分是PhotoShop导出,所以其中不包含照片;
- WebP Lossless:通过
cwebp -q 100 "$file" -o "$file.webp" -lossless
生成 - PNG24:ImageOptim批量压缩生成
- WebP Lossless vs PNG24:
(PNG24大小 - WebPLossless大小) / 原始大小
- WebP Loss:通过
cwebp -q 100 "$file" -o "$file.webp"
生成3 - PNG8:
pngquant --force --ext .png *.png
-> ImageOptim批量压缩生成 - WebP vs PNG8:
(PNG8大小 - WebPLoss大小) / 原始大小
所得数据如下:
图片 | 原始大小(字节) | WebP Lossless | PNG24 | WebP Lossless vs PNG24 | WebP Loss | PNG8 | WebP vs PNG8 |
---|---|---|---|---|---|---|---|
image1.png | 634,022 | 456,208 | 567,430 | -17.54% | 192,208 | 258,189 | -10.41% |
image2.png | 396,419 | 108,574 | 212,323 | -26.17% | 99,616 | 86,209 | 3.38% |
image3.png | 202,683 | 100,652 | 141,316 | -20.06% | 81,742 | 55,635 | 12.88% |
image4.png | 184,871 | 66,424 | 100,781 | -18.58% | 49,570 | 41,843 | 4.18% |
image5.png | 173,382 | 85,234 | 98,493 | -7.65% | 43,020 | 39,856 | 1.82% |
image6.png | 173,382 | 85,234 | 98,493 | -7.65% | 43,020 | 39,856 | 1.82% |
image7.png | 92,715 | 37,084 | 58,920 | -23.55% | 62,282 | 31,929 | 32.74% |
image8.png | 88,804 | 58,560 | 74,963 | -18.47% | 72,940 | 30,366 | 47.94% |
image10.png | 81,941 | 41,132 | 46,453 | -6.49% | 21,864 | 20,795 | 1.30% |
image9.png | 81,941 | 41,132 | 46,453 | -6.49% | 21,864 | 20,795 | 1.30% |
image11.png | 50,071 | 18,500 | 29,617 | -22.20% | 15,948 | 16,072 | -0.25% |
image12.png | 38,768 | 14,556 | 30,474 | -41.06% | 32,098 | 15,727 | 42.23% |
image13.png | 32,019 | 20,544 | 22,851 | -7.21% | 10,120 | 17,474 | -22.97% |
image14.png | 25,142 | 1,876 | 2,893 | -4.05% | 3,370 | 2,876 | 1.96% |
image15.png | 24,934 | 1,688 | 2,647 | -3.85% | 2,956 | 2,638 | 1.28% |
image16.png | 24,775 | 1,708 | 2,686 | -3.95% | 2,986 | 2,672 | 1.27% |
image17.png | 24,268 | 1,916 | 2,873 | -3.94% | 3,224 | 2,862 | 1.49% |
image18.png | 23,764 | 14,062 | 15,699 | -6.89% | 7,796 | 12,053 | -17.91% |
image19.png | 23,324 | 11,414 | 16,165 | -20.37% | 19,610 | 8,739 | 46.61% |
image20.png | 23,274 | 1,332 | 1,971 | -2.75% | 1,794 | 2,169 | -1.61% |
image21.png | 23,083 | 9,866 | 17,368 | -32.50% | 8,366 | 9,856 | -6.45% |
image22.png | 22,347 | 1,342 | 1,998 | -2.94% | 1,866 | 2,177 | -1.39% |
image23.png | 21,356 | 8,946 | 10,899 | -9.14% | 16,456 | 9,089 | 34.50% |
image24.png | 21,249 | 8,648 | 11,021 | -11.17% | 16,108 | 6,719 | 44.19% |
image25.png | 21,092 | 11,978 | 14,861 | -13.67% | 6,412 | 11,951 | -26.26% |
image26.png | 18,371 | 7,670 | 9,797 | -11.58% | 13,012 | 8,376 | 25.24% |
image27.png | 17,826 | 7,174 | 8,962 | -10.03% | 12,464 | 5,934 | 36.63% |
image28.png | 17,300 | 8,012 | 12,771 | -27.51% | 9,196 | 8,960 | 1.36% |
image29.png | 17,036 | 6,964 | 8,958 | -11.70% | 11,788 | 5,105 | 39.23% |
image30.png | 15,814 | 8,192 | 10,126 | -12.23% | 5,156 | 8,222 | -19.39% |
image31.png | 14,561 | 6,836 | 10,380 | -24.34% | 5,432 | 6,708 | -8.76% |
image32.png | 13,288 | 5,512 | 7,129 | -12.17% | 10,156 | 4,085 | 45.69% |
image33.png | 13,225 | 6,960 | 7,645 | -5.18% | 17,464 | 6,683 | 81.52% |
image34.png | 11,500 | 4,990 | 8,277 | -28.58% | 9,354 | 4,878 | 38.92% |
image35.png | 11,307 | 610 | 1,355 | -6.59% | 2,168 | 1,355 | 7.19% |
image36.png | 11,239 | 588 | 1,355 | -6.82% | 2,582 | 1,355 | 10.92% |
image37.png | 8,703 | 546 | 1,162 | -7.08% | 1,836 | 1,162 | 7.74% |
image38.png | 8,468 | 2,928 | 4,335 | -16.62% | 4,892 | 4,036 | 10.11% |
image39.png | 6,665 | 2,944 | 4,350 | -21.10% | 3,258 | 3,818 | -8.40% |
image40.png | 5,382 | 360 | 794 | -8.06% | 1,378 | 794 | 10.85% |
image41.png | 5,362 | 372 | 794 | -7.87% | 1,160 | 794 | 6.83% |
image42.png | 5,234 | 1,384 | 1,689 | -5.83% | 4,476 | 1,689 | 53.25% |
image43.png | 4,854 | 1,684 | 2,457 | -15.93% | 3,806 | 2,457 | 27.79% |
image44.png | 4,725 | 774 | 1,369 | -12.59% | 1,946 | 1,369 | 12.21% |
image45.png | 4,314 | 336 | 703 | -8.51% | 1,068 | 703 | 8.46% |
image46.png | 3,831 | 1,122 | 1,398 | -7.20% | 4,714 | 1,398 | 86.56% |
image47.png | 3,114 | 1,198 | 1,715 | -16.60% | 2,444 | 1,715 | 23.41% |
image48.png | 3,107 | 1,018 | 1,268 | -8.05% | 2,862 | 1,268 | 51.30% |
image49.png | 2,832 | 880 | 1,408 | -18.64% | 2,450 | 1,408 | 36.79% |
image50.png | 2,825 | 862 | 1,070 | -7.36% | 2,632 | 1,070 | 55.29% |
image51.png | 2,642 | 874 | 1,379 | -19.11% | 2,404 | 1,317 | 41.14% |
image52.png | 2,611 | 970 | 1,421 | -17.27% | 2,176 | 1,473 | 26.92% |
image53.png | 2,581 | 486 | 794 | -11.93% | 1,150 | 794 | 13.79% |
image54.png | 2,464 | 834 | 1,288 | -18.43% | 2,472 | 1,256 | 49.35% |
image55.png | 2,316 | 924 | 1,290 | -15.80% | 2,006 | 1,343 | 28.63% |
image56.png | 2,310 | 756 | 950 | -8.40% | 3,042 | 950 | 90.56% |
image57.png | 2,254 | 628 | 1,065 | -19.39% | 1,306 | 1,041 | 11.76% |
image58.png | 2,035 | 732 | 961 | -11.25% | 1,846 | 961 | 43.49% |
image59.png | 1,988 | 738 | 1,079 | -17.15% | 2,152 | 1,112 | 52.31% |
image60.png | 1,818 | 748 | 1,031 | -15.57% | 1,998 | 1,067 | 51.21% |
总计 | 2,763,528 | 1,296,216 | 1,752,173 | -16.50% | 995,482 | 845,203 | 5.44% |
从表格中可以看出,无损压缩对比中,总体WebP格式比PNG24格式平均节约了16.50%的大小,在所有尺寸下WebP表现都比PNG24优秀。
对比有损压缩来看,PNG8略胜WebP,不过总体也只小5.44%。另一方面,小文件的压缩(13KB以下)PNG8有很大的优势。大部分情况下PNG8压缩的情况都比WebP好。
这样看来,使用WebP图片,无损压缩格式还是有一定优势;如果项目主要用损压缩PNG8,切换WebP可能没有太大的优势 。
浏览器兼容WebP图片现状
从CanIUse.com的WebP兼容数据我们可以看到,只有Chrome/Android浏览器(Google自家的)和Opera浏览器有对WebP图片的支持,IE/Edge/Firefox/Safari系列都不支持。
即使是全球范围内也只有72.9%的兼容率,中国地区差不多是72.58%。所以如果要使用WebP图片需要考虑一大部分浏览器不兼容的现状。
如何解决兼容性问题
作为一名端工程师,很大一部分的工作内容都与兼容性相关。总结来说,根据资源从获取到展示的流程,解决兼容性有两个方向的思路:
拦截器(Interceptor)
拦截器的作用是在获取资源的出入口进行拦截,拦截后可以做替换、转换等操作。拦截可以通过代码自身完成,也可以借助外部环境完成。
解析器(Parser)
解析器即在运行环境注入解析库,使得运行环境能够读取和解析对应格式的资源。
对于客户端(iOS, Android等)来说,各种第三方库都可以说是解析器,如果平台不支持WebP格式解析,可以打包第三方库(比如libwebp)到App包里。客户端解决兼容性相对容易一点,因为基本上使用图片只有一个入口引用,分别在ObjectiveC/Swift/Java代码中。而HTML5项目中则对应着HTML/CSS/JS三个入口,我们注入第三方库(比如libwebpjs)只能解决JS引用的问题。
所以对于浏览器来说,兼容性处理就麻烦的多。浏览器是各个厂商提供的,我们也不能强行让用户给浏览器装插件。另一方面,HTML5的发展趋势也是去插件化,从Flash,WebGL等技术的发展趋势也能够看出。插件这一条路也是不可取的。
HTML5 WebP 解决方案
根据前面的讨论,项目中引入WebP有一定的优势,那么要在HTML5项目使用WebP图片,解决方案有哪些呢?
-
<picture>
标签是根据浏览器能力自动选择图片格式的解决方案,思路很不错,目前其兼容性如下:可以看到大部分浏览器已经支持了这个标签。主要问题在于Android平台在Android 5-6.x才支持。根据Android官方统计数据,目前还有接近50%的Android设备是在5.0以下的。
iOS平台在9.3后的系统才支持这个标签。根据David Smith的统计数据,iOS还有大概18%的设备是10.x以下的。
TODO 在不支持
<picture>
标签的浏览器上运行能否正常显示图片。- 优点是通用性好,使用容易,不支持WebP格式的可以自动fallback到其他格式。
- 缺点是iOS和Android均有一部分比例的系统不支持此标签。
客户端拦截:如果项目只运行在客户端(比如iOS,Android的Webview)环境中,可以在客户端进行拦截Webview中的图片引用,将WebP图片转码成PNG/JPEG后再返回给Webview。
- 优点是实现相对比较容易,只需要iOS端作处理即可,Android和HTML5端不需要处理。
- 缺点是与运行环境耦合,脱离了特定的环境无法运行,对于需要同时支持客户端和浏览器环境的项目不适用;另一方面,转码很消耗客户端性能。
资源加载拦截:JS实现统一的资源加载器,所有的图片资源加载时,加载器根据浏览器的能力去分别加载PNG/WebP格式资源。
- 优点是通用性好,维护容易,只需要在JS入口做控制。
- 缺点是无法处理HTML/CSS中的图片引用,只对游戏等特定应用场景适用。
多版本代码:根据浏览器是否支持WebP,加载不同版本的JS和CSS文件。大部分工作在编写构建流程,业务逻辑本身不用太多改动。
- 优点是通用性好,能适应不同的运行环境,HTML,JS和CSS都能控制。
- 缺点是打包流程会变得比较复杂,需要预先生成多套代码和资源。另一方面生成的一套WebP代码和资源只在Android平台有用。
libwebpjs:libwebpjs能够通过js,在WebP和其他图片格式之间进行encode和decode,使得不支持WebP格式的浏览器也能够解析图片。
- 优点是能在所有浏览器享受WebP格式的优势。
- 缺点是图片实时转换消耗浏览器性能,在网站上观察转换为PNG/JPEG有几百毫秒不等的延时;库很大,minify之后仍然有69KB;无法处理HTML/CSS中的图片引用。
以上几个方案对HTML4, CSS, JS几个入口WebP图片引用的支持情况如下:
方案 | 类型 | HTML引用 | CSS引用 | JS引用 |
---|---|---|---|---|
picture | 拦截器 | ✅ | ❌ | ❌ |
客户端拦截 | 拦截器 | ✅ | ✅ | ✅ |
资源加载拦截 | 拦截器 | ❌ | ❌ | ✅ |
多版本代码 | 拦截器 | ✅ | ✅ | ✅ |
libwebpjs | 解析器 | ❌ | ❌ | ✅ |
在iOS,Android客户端(Webview)和浏览器运行环境中兼容情况如下(✅使用WebP,❌不使用WebP,⚠️部分用WebP,❗️运行可能出错):
方案 | iOS客户端 | Android客户端 | iOS浏览器 | Android浏览器 |
---|---|---|---|---|
picture | ❗️⚠️部分浏览器 | ❗️⚠️部分浏览器 | ❗️⚠️部分浏览器 | ❗️⚠️部分浏览器 |
客户端拦截 | ✅需要拦截处理 | ✅ | ❗️ | ❗️⚠️部分浏览器 |
资源加载拦截 | ⚠️JS | ⚠️JS | ⚠️JS | ⚠️JS |
多版本代码 | ❌ | ✅ | ❌ | ⚠️部分浏览器 |
libwebpjs | ⚠️JS | ⚠️JS | ⚠️JS | ⚠️JS |
从表格可以看出,对于常规的HTML5项目来说:
<picture>
:排除,兼容性不太好,目前阶段还不适用;- 客户端拦截:排除,因为在浏览器环境下运行可能不正常;
- 资源加载拦截:排除,不能处理HTML和CSS的图片引用,JS里也要单独写加载器;
- 多版本代码:相比之下较为理想,能够自动处理HTML, CSS, JS中的图片引用,运行兼容性也能保证,唯一的缺点是对于不支持WebP的浏览器得fallback回到PNG图片。
- libwebpjs:排除,缺点太多,库大、转码耗性能、不能处理HTML和CSS的图片引用。
多版本代码解决方案的实现
我用Node.js + React + Webpack + CSS Modules等技术写了个demo项目,源代码见https://github.com/swenyang/webp-multi-versioning。
主要思路如下:
- 用Node.js写build脚本,从Webpack配置编译出不用WebP和用WebP的两种版本代码。
HTML引用Webpack生成的js脚本时,不直接用
<script src="xxx">
引入,先判断浏览器的WebP能力,再对应载入WebP版本的代码或者非WebP版本的代码:var isWebPSupported; (function () { var WebP = new Image(); WebP.onload = WebP.onerror = function () { isWebPSupported = WebP.height === 2; var name = isWebPSupported ? 'main.f041d9036d33197bd734.js' : 'main.9f0e87ed2db271e1a42b.js'; var s = document.createElement('script'); s.src = name; document.body.appendChild(s); }; WebP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA'; })();
在线的demo请见https://swenyang.github.io/webp-multi-versioning/。
总结
虽然WebP诞生至今已经接近7个年头,但是浏览器支持一直局限于Google自家出的Chrome和Android浏览器、Opera浏览器,其他系列鲜有支持。这个背景下,加上HTML5的特殊性,WebP解决方案的优势并不非常明显,最好结合所在团队和业务的情况因地制宜。
本文给出了5个解决方案,每个方案有各自的优劣势,总结适用场景分别如下:
<picture>
:适用于不太考虑兼容低版本Android和iOS的项目(前文讨论分别有约50%和18%,比例不小),方案实现容易;- 客户端拦截:适用于只运行在客户端环境内的项目(Hybrid App),方案实现容易;
- 资源加载拦截:适用于只从JS代码加载图片的项目,目前来看比较偏向于游戏场景,方案实现比较容易;
- 多版本代码:适用于大部分项目,兼容性好,但方案实现比较难;
- libwebjs:适用于不太考虑性能、且只从JS代码加载图片的项目(感觉除了demo外没有特别好的使用场景)。
对于多版本代码方案,写了一个demo实现了自动构建,如果考虑使用WebP可以参考借鉴一下,项目地址是https://github.com/swenyang/webp-multi-versioning,欢迎指正。
1. WebP对比PNG/JPEG格式图片的数据来源于Google WebP官方介绍https://developers.google.com/speed/webp/。 ↩
2. 本文讨论的HTML5项目(也可称前端项目)是指既可能运行在浏览器,也可能运行在iOS/Android App的Webview中的HTML5代码,包括HTML/CSS/JS等。 ↩
3. 假设PNG24 -> PNG8只有色彩损失,其他品质不变,所以使用q=100 ↩
4. React的JSX语法看做是HTML的一部分。 ↩