一个老生常谈的话题了,关于博客图片资源的优化,包括但不限于图片压缩、更优文件格式的使用以及浏览器兼容性处理。今天我打算重新整理和归纳一下。以前本站使用过基于 gulp 的图片压缩和 webp 格式转换,但由于效果不佳,转而使用了腾讯云 CDN 的数据万象服务。

现在嘛,消费降级,还是减少对付费服务的依赖吧。同时,我在刷博客时看到了 Heo 的 实现全站图片使用 avif 格式,替代臃肿的 webp 教程 ,新的图片格式真是如雨后春笋般层出不穷。Whatever,那么重新加入本地处理能力了,除了 webp,现在还要支持 avif。

一、图片压缩转换

我们有两个目标:首先是压缩原始图片,其次是将原始图片转换为 webp 和 avif 格式。考虑到 Hexo 是基于 Node.js 引擎的,所以决定使用 lovell/sharp 这个前端库。刚好,这个库不仅能压缩图片,还能转换图片格式,并且可以在 Node.js 环境中运行。不二之选,完美至极。

处理思路:

  • 首先对原始图片进行压缩,生成体积小于原始文件的压缩版本。
  • 仅转换特定格式的图片,通常情况下,只需处理 jpgjpegpng 等格式。
  • 性能优化:如果待处理的目录中已经存在同名的 webp 或 avif 文件,就跳过该文件,不再进行处理。

补充内容:

Sharp 库支持 GIF 压缩,但会丢失动图信息,并且不支持将 GIF 转换为 webp 和 avif 格式。因此,额外添加一个判断,如果当前系统中安装了 ffmpeg,则利用它来完成 GIF 的格式转换。

git to webp
ffmpeg -i ${inputFilePath} -c:v libwebp -lossless 0 -q:v 80 -loop 0 -an -vsync 0 ${outputWebPPath}
gif to avif
ffmpeg -i ${inputFilePath} -c:v libsvtav1 -qp 40 ${outputAvifPath}

完整脚本内容如下,使用时需安装对应依赖:

假设这个脚本位于 .tools/sharp.js 文件中,并且你的所有图片文件都存放在 ./source/image 目录下,你只需要在自动化部署代码中添加以下内容,即可实现自动图片转换:

node ./tools/sharps.js

转换后的文件会保存在同一路径下,因此你的仓库体积可能会增加一点点。如果你介意空间占用,但不介意在部署时花费更多时间,那么你可以选择对 ./public/image 目录进行处理:

node ./tools/sharps.js ./public/image

看看效果,这是三张图片:

二、图片资源调用

只关心 Chromium 系的兼容性,webp 32 支持,avif 85 支持。avif 的兼容性有那么一点点欠佳。传统技能,使用 picture 标签提供回退图像(Chromium 38)。

HTML <picture> 元素 通过包含零或多个 source 元素和一个 img 元素来为不同的显示/设备场景提供图像版本。浏览器会选择最匹配的子 <source> 元素,如果没有匹配的,就选择 <img> 元素的 src 属性中的 URL。然后,所选图像呈现在 <img> 元素占据的空间中。

也就是对于常规 <img> 标签,替换为:

<picture>
<source srcset="diagram.avif" type="image/avif" />
<source srcset="diagram.webp" type="image/webp" />
<img src="diagram.png" alt="数据通道示意图" />
</picture>

处理思路:

  • 遍历所有 HTML 文件,利用 JSDOM 将其转换为 NodeList 对象,以便于后续处理。

  • 过滤出需要处理的 img 标签,对其进行替换,若转换后的文件体积比原文件更大,则不进行替换。

对于本站,所有位于 source 目录中的图片都将通过域名 https://static.inkss.cn 进行加载。因此,如果图片的加载地址以该域名开头,就表示这是一个可以处理的图片引用。

三、图片时间戳

CDN 里的缓存时间设置得很长,如浏览器图片缓存的过期时间为七天。这导致如果图片有变动,除非强制刷新,否则二次访问时客户端是无法立即获取到最新的图片内容。

所以做如下判断:从 git 读取最近七天的提交记录,过滤出所有涉及 img 文件变动的记录。接着,类似于“图片资源调用”中的处理方法,遍历 HTML 文件中的所有 img 标签,并与前面的记录做比较。如果匹配,则在地址后追加时间戳。

git log --since="7 days ago" --name-only --pretty=format: -z

评论