WebP 图片优化在 Hexo 上的最佳尝试
早已对 WebP.sh 垂涎三尺,但是出于一些个人情怀,并且为了将维护成本降至足够低,我选择了使用 Hexo 生成、部署在 GitHub Page 上的静态博客。虽然手头上确实也有些闲置 VPS,但是不想为此打乱阵型,自然也是享受不到 WebP.sh 真正的「无痛迁移」了。
更何况,即便浏览器对 WebP 的 支持情况 已经接近 80%,却依然有些主流浏览器如 Safari、IE 仍不支持,所以不能直接转用 WebP。
那难道,在 Hexo 等静态博客上就没有一套足够简单 WebP 解决方案吗?并不,我倒是尝试出一种勉强算是「无痛迁移」的实现。
优劣
本文步骤较多,所以先列举这套方案的优劣。仅供参考,方便你决定是否这么做。
优点
首先达成一个共识:所有网站都应该高效地压缩图像,并且由于压缩图像这项工作重复且繁琐,图像优化应自动化完成。
- 初次配置完成,日后使用无需任何操作便可全自动切换 WebP 图片格式
- 对于不支持的浏览器,会自动回退到 JPEG/PNG 等传统格式
- 提前生成好两份文件而非请求时计算,节省算力且响应更迅速
无论是出于节省带宽亦或是优化资源配置,你都应该尝试优化图像资源。而更小的页面资源链意味着更优的网页性能,这又能为你的网站获取更多的 SEO 分数。
劣势
人无完人,金无足赤。这种纯静态方案自然有着难以弥补的痛点。
- 图片必须放在博客
source
文件夹内,不能是第三方图床(除非你愿意为每张图片手动配置) - 基于 gulp,每次构建部署时间可能会显出增长(其实你完全可以丢给 CI 完成,这算半个缺点吧)
在你权衡完优劣打算继续看后续详细步骤前,我想先说明:像是阿里云、腾讯云、又拍云等诸多云服务已提供自适应 WebP 功能,只需开关即可。如果你能做好一定的防护措施,使用他们的服务似乎也是一种好选择。当然,由于某些原因使用不了国内云服务的我只好另辟蹊径,才促成此文。
WebP 准备
WebP 是 Google 最新开发的新图像格式,旨在以可接受的视觉质量为无损和有损压缩提供较小的文件大小。有损模式下比 JPEG 小 25% - 34%,无损模式下较 PNG 小 26%。
如果你想详细了解这其中的技术细节,可以阅读 Google 开发者文章 WebP 压缩技术。
根据 Hexo 规则,每次构建时会将 source/
资源目录下的所有资源(包括图片资源)放入 public/
网站目录中,随后将 public/
网站目录部署到相应位置。所以,为了方便后续自动化实现,请将所有图片资源放于 source/
资源目录下。本文中所有图像资源示例均放置于 source/assets/image/
下以便演示。
Gulp,一款基于 node 实现的自动化构建工具。这里将借助 Gulp 以及 gulp-webp 全自动将图片转为 WebP 格式。
首先是安装:
npm install --global gulp-cli
npm install --save-dev gulp gulp-webp
并在博客根目录下创建 gulpfile.js
文件:
const gulp = require('gulp');
const webp = require('gulp-webp');
gulp.task('default', () => (
gulp.src('./assets/image/*.{jpg,png,jpeg}')
.pipe(webp({
quality: 75,
preset: 'photo',
method: 6
}))
.pipe(gulp.dest('./assets/image'))
));
这样每次要批量转换位于 source/assets/image/
下的图片格式时,只需要执行:
gulp
更新:
上述方法将图片转为 WebP 格式还是有点不够优雅,它只能处理
source/assets/image/
下图片,而对source/assets/image/a/
之类包含在子文件夹内图片就无动于衷了。当然,这不过是再递归文件夹的事,但已经有前人写过命令行批量将图片转换 WebP 格式的 模块,我就不再造轮子了。
npm install --save-dev webp-batch-convert
npx cwebp-batch --in assets/image --out assets/image -q 75 -quiet
至此,所有准备工作都已经完成。在 source/assets/image/
下的图片都同时拥有一份传统格式和一份 WebP 格式的文件,这两份文件仅后缀不同。
启用与回退
前面提到,WebP 并没有得到完全支持,不能直接在网站中全部转换。对于不支持 WebP 的浏览器最终可能根本无法显示图像,我们当然不希望发生这种情况。
一开始我的想法是,通过引入 JavaScript 判断浏览器,如果不兼容则批量切换回原图。且不提我不希望引入过多 JS 这种怪癖,有些浏览器要视版本而定,有些小众浏览器压根没有记录,更何况 user-agent
还是可以伪造的。
后来注意到 <picture>
标记,该标记可以使用多个 <source>
元素和一个 <img>
标记。将 WebP 格式文件放入 <source>
元素中,并将应用更加广泛的 JPEG/PNG 等传统格式文件放入 <img>
标记中。对于能够理解 image/webp
源的浏览器会加载 <source>
标记内的 WebP 格式文件,而不理解的浏览器则回退至 <img>
标记内的传统格式文件。
<picture>
<source srcset="/assets/image/beauty.webp" type="image/webp">
<img src="/assets/image/beauty.jpg" alt="beauty">
</picture>
只要是能够理解 WebP 格式的浏览器都会加载 WebP 格式的图片,不理解的浏览器也能展示传统格式图片而不会完全不显示图片。这样一来,摆脱了对浏览器型号判断的硬性依赖,即便后续浏览器更新导致的支持情况变化,或者是一些没有姓名的小众浏览器,都能较好的处理,无需再更改配置。
在「Hexo 图片 lazyload」尝试了在 scripts/
下放一个用于转换的脚本,每次 Generate 的时候就会自动转换。这种操作同样可以应用于此。
首先在博客根目录配置文件中追加:
use_webp: true
接下来在 scripts/
下:
// scripts/webp/index.js
'use strict';
if (hexo.config.use_webp) {
hexo.extend.filter.register('after_render:html', require('./lib/process').processWebP);
}
// scripts/webp/lib/process.js
'use strict';
const fs = require('hexo-fs');
function webpProcess(htmlContent) {
return htmlContent.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
if (/webp-comp/gi.test(p1) || !/\/assets\/image\/(.*?)\.(jpg|jpeg|png)/gi.test(p2)) {
return str;
}
return `<picture><source srcset="${p2.replace(/\.(jpg|jpeg|png)/gi, '.webp')}" type="image/webp">${str.replace('<img', '<img webp-comp')}</picture>`;
});
}
module.exports.processWebP = function (htmlContent) {
return webpProcess.call(this, htmlContent);
}
至此,你的网站已经可以自动判断并显示 WebP 格式图片了。
兼容 Lazyload
关于如何实现 Lazyload 不是本文的重心,你可以参考我之前写的一篇文章「Hexo 图片 lazyload 的更优尝试」进行适配。
在图片进入可视区域前,我们自然也是希望 <source>
元素中同为 缩略图/占位元素 而非原图,不然懒加载就没有意义了。所以我们需要修改上述代码,请确保 webp
脚本在 lazyload
脚本之后执行。
为了方便,我将这两个 scripts 结合到一起。
// scripts/lazy-webp/index.js
'use strict';
if (hexo.config.lazyload && hexo.config.lazyload.enable === true) {
if (hexo.config.lazyload.onlypost) {
hexo.extend.filter.register('after_post_render', require('./lib/process').processPost);
} else {
hexo.extend.filter.register('after_render:html', require('./lib/process').processSite);
}
}
if (hexo.config.use_webp === true) {
hexo.extend.filter.register('after_render:html', require('./lib/process').processWebP);
}
// scripts/lazy-webp/lib/process.js
'use strict';
const fs = require('hexo-fs');
function lazyProcess(htmlContent) {
let loadingImage = this.config.lazyload.loadingImage || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEXMzMyWlpYU2uzLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==';
return htmlContent.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function (str, p1, p2) {
// might be duplicate
if (/data-srcset/gi.test(str)){
return str;
}
if (/src="data:image(.*?)/gi.test(str)) {
return str;
}
if (/no-lazy/gi.test(str)) {
return str;
}
return str.replace(p2, p2 + '" class="lazyload" ' + 'data-srcset="' + p2 + '" srcset="' + loadingImage);
});
}
function webpProcess(htmlContent) {
return htmlContent.replace(/<img(.*?)data-srcset="(.*?)"(.*?)srcset="(.*?)"(.*?)>/gi, function (str, p1, p2, p3, p4) {
if (/webp-comp/gi.test(p1) || !/\/assets\/image\/(.*?)\.(jpg|jpeg|png)/gi.test(p2)) {
return str;
}
return `<picture><source class="lazyload" data-srcset="${p2.replace(/\.(jpg|jpeg|png)/gi, '.webp')}" srcset="${p4}" type="image/webp">${str.replace('<img', '<img webp-comp')}</picture>`;
});
}
module.exports.processPost = function(data) {
data.content = lazyProcess.call(this, data.content);
return data;
};
module.exports.processSite = function (htmlContent) {
return lazyProcess.call(this, htmlContent);
};
module.exports.processWebP = function (htmlContent) {
return webpProcess.call(this, htmlContent);
}
CI 持续集成
我之前也写过关于 Hexo 持续集成部署文章「初探无后端静态博客自动化部署方案」,如有兴趣可配合该文阅读。
至于选择持续集成的理由,一方面可以节省本地图片格式转换的时间,另一方面 WebP 格式的图片甚至都不用保存在本地。
这里我选择了我正在使用的 GitHub Actions,其他几种也可类比。
name: Hexo Deploy Automatically
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Node.js envs
uses: actions/setup-node@v1
with:
node-version: "10.x"
- name: Hexo deploy
env:
HEXO_DEPLOY_KEY: ${{ secrets.HEXO_DEPLOY_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$HEXO_DEPLOY_KEY" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
ssh-keyscan github.com >> ~/.ssh/known_hosts
git config --global user.name "Your GitHub UserName"
git config --global user.email "you@example.com"
npm i -g hexo-cli gulp-cli
npm i
hexo cl && gulp
hexo g -d
后
在迁移至 WebP 之前,我一直使用 MozJPEG 按质量分数 75/60 压缩。但是 JPEG 会出现难看的块效应伪影,也有读者向我抱怨说「博客的图片压缩到快要没法看了」。
后来在好朋友 木子 的博客看到 WebP.sh 的介绍,但是要维护服务器,只好放弃。
这次尝试,尽管不能像 WebP.sh 那样真正什么都不用管,但也算一劳永逸的方案。重点是,部署在任何地方的纯静态 Hexo 博客都能用上。
之前的原图都散落在各个位置,我也实在没有精力一个个找回来了。不过日后博客图片将采用 WebP 无损压缩,体验应该会上来的说。
参考链接: