写了好久一个名为efc的项目结果到现在还没有弄完,正好服务器上部署的亲友的网站放的图片奇大无比,做了个压缩来顺便水一篇。
什么是WebP?
WebP是Google在2010年发布的一种同时支持有损和无损压缩的图片格式,并且支持Alpha透明通道、ICC色彩配置、XMP诠释资料,而且还有静态和动态两种模式。也就是说,完全可以用WebP来代替我们常见的JPEG,PNG和GIF格式。事实上WebP的设计目标就是在减少文件容量同时,达到和JPEG,PNG,GIF格式相同的图片质量,并希望借此能够减少图片档在网络上的发送时间。
那么压缩的效果如何?有损压缩比JPG小25%~33%,无损压缩比PNG小25%(因为PNG是无损的,如果用有损,同样支持透明通道,能比PNG小60%~80%)。这些大小比较我没有做具体的测试,都是查到的内容。在进行WebP有损压缩的时候可以选择一个从1到100的质量参数,通常使用的时候都会选取75~80作为大小和质量之间的一个平衡,我猜这些大小比较也是在这个质量区间的。
尽管本地的各种软件或许不支持WebP格式,在WebP的主场浏览器这里,截至目前支持了全球96.96%的用户的浏览器,不支持的仅有一些非常老旧版本的浏览器,还有已经被Edge取代的Internet Explorer。具体最新支持比例统计可以查看这里。
Hexo挂载脚本
因为专门写一个npm package太麻烦了,于是选择了直接在Hexo的目录里建了个scripts文件夹直接挂载一个js文件,用到了下面几个库:
1 2 3 4
| const cheerio = require('cheerio'); const sharp = require('sharp'); const path = require('path'); const fs = require('fs-extra');
|
这个脚本具体都干了什么呢,首先是:
1 2 3 4 5 6 7 8
| function ensureWebpAsync(originalAbsPath, hexo) { const webpAbsPath = originalAbsPath.replace(/\.(png|jpg|jpeg)$/i, '.webp'); if (fs.existsSync(webpAbsPath)) return true; sharp(originalAbsPath) .webp({ quality: 80 }) .toFile(webpAbsPath); return false; }
|
这里检测了一下是不是已经有替换过的WebP文件,避免在每次生成的时候都压缩一遍。sharp是用来把图片压缩成WebP格式的库,这里sharp(filePath).webp({ quality: 80 })中的quality就是前面提到的压缩品质,也可以用{ lossless: true }来进行无损压缩。
因为我用的GIF图很少,而且GIF图通常都是已经高压的了,这里就只对PNG和JPG进行压缩。
除了生成文件之外,还要修改文章当中对应图片的地址:
1 2 3 4 5 6 7 8
| function replaceUrlsInText(text, baseDir, hexo) { const urlRegex = /url\(\s*['"]?(.*?\.(png|jpg|jpeg))['"]?\s*\)/gi; return text.replace(urlRegex, (match, urlPath, ext) => { if (urlPath.startsWith('http') || urlPath.startsWith('//')) return match; if (ensureWebpAsync(path.resolve(baseDir, urlPath), hexo)) return match.replace(urlPath, urlPath.replace(/\.(png|jpg|jpeg)$/i, '.webp')); return match; }); }
|
这里做了个处理就是如果图片地址的开头是http或者//就代表这个图片并不是存在我的服务器上的,而是调用的外部图片,不需要进行替换。
定义了这两个函数之后,就是要在Hexo生成静态文件的时候挂载上去进行修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| function replaceUrlsInText(text, baseDir, hexo) { const urlRegex = /url\(\s*['"]?(.*?\.(png|jpg|jpeg))['"]?\s*\)/gi; return text.replace(urlRegex, (match, urlPath, ext) => { if (urlPath.startsWith('http') || urlPath.startsWith('//')) return match; if (ensureWebpAsync(path.resolve(baseDir, urlPath), hexo)) return match.replace(urlPath, urlPath.replace(/\.(png|jpg|jpeg)$/i, '.webp')); return match; }); }
hexo.extend.filter.register('after_render:html', function (htmlContent, data) { const $ = cheerio.load(htmlContent); const publicDir = hexo.public_dir; let modified = false;
$('img').each(function () { const src = $(this).attr('src'); if (!src) return; if (src.startsWith('http') || !/\.(png|jpg|jpeg)$/i.test(src)) return;
const imgAbsPath = path.join(publicDir, src); if (ensureWebpAsync(imgAbsPath, hexo)) { $(this).attr('src', src.replace(/\.(png|jpg|jpeg)$/i, '.webp')); modified = true; } });
$('*').each(function () { const style = $(this).attr('style'); if (!style) return;
const newStyle = replaceUrlsInText(style, publicDir, hexo); if (newStyle !== style) { $(this).attr('style', newStyle); modified = true; } });
return modified ? $.html() : htmlContent; });
hexo.extend.filter.register('after_render:css', function (cssContent, data) { const publicDir = hexo.public_dir; const cssSourceDir = path.dirname(data.path); const cssOutputDir = cssSourceDir.replace(hexo.source_dir, publicDir);
return replaceUrlsInText(cssContent, cssOutputDir, hexo); });
|
很简单的一件事情,文章里面使用图片的方法有三种可能:
- 直接以
<img src=图片地址>的形式,也是在Markdown中最终在网页中呈现的形式
- 以
<div style="background-image: url(图片地址)">这样作为某个元素背景图的形式
- 在css文件中设置的背景图的形式
于是就在这三种情况对应的位置查找图片地址并使用刚才的两个函数进行替换。
下一步
因为本站至少目前来说也不是一个会挂载很多图片的地方,我自己的图像大小都有控制,加载速度和流量来看都没有什么大问题,加上了这个WebP压缩实际提升的效果也没有很明显。但后面也说不定就要传大量的图片,这个时候其实有比WebP更好用的选择:AVIF,能在WebP的基础上再压缩20%~30%,代价是压缩速度会慢很多。
Hexo是静态页面,压缩慢倒是也没太有所谓。代码层面来讲,只需要把sharp部分由.webp()改为.avif({ quality: 30 })就可以了,但等到需要的时候再考虑吧。