原本博客用的都是普通图片,就算有懒加载,一堆圈圈一起转也惹人心烦。现在改为了原图 /webp 的自适应切换,效果好上不少。
前期准备
首要任务是拿到 webp 格式的图片,这个看自己。像我用的 vps 上的Lsky Pro,本地存储。有高性能 vps 可以试试用Webp-Server配合。但我的轻量应用承受不起,遂作罢。改为了定时 shell 脚本,一分钟触发一次:
#!/bin/bash
find . -type f -iname "*.png" | while read file; do
if [ ! -f "${file%.*}.webp" ]; then
cwebp -q 85 "$file" -o "${file%.*}.webp"
fi
done
find . -type f -iname "*.jpg" | while read file; do
if [ ! -f "${file%.*}.webp" ]; then
cwebp -q 85 "$file" -o "${file%.*}.webp"
fi
done
find . -type f -iname "*.jpeg" | while read file; do
if [ ! -f "${file%.*}.webp" ]; then
cwebp -q 85 "$file" -o "${file%.*}.webp"
fi
done
find . -type f -iname "*.tif" | while read file; do
if [ ! -f "${file%.*}.webp" ]; then
cwebp -q 85 "$file" -o "${file%.*}.webp"
fi
done脚本运行时会遍历自己所在的文件夹及其子文件夹,转换所有没有对应 webp 格式的图片(png,jpg、jpeg与tiff)为 webp 图片(原图还在,放心)。
这段脚本中使用了cwebp指令,它来源于libwebp。安装可以参考下方:
# 安装编译器以及依赖包
yum install -y gcc make autoconf automake libtool libjpeg-devel libpng-devel
# 请到官网下载最新版本,版本列表:https://storage.googleapis.com/downloads.webmproject.org/releases/webp/
wget https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.2.4.tar.gz
# 解压
tar -zxvf libwebp-1.2.4.tar.gz
# 进入目录
cd libwebp-1.2.4
# 源代码安装环境检查
./configure
# 编译
make
# 安装
make install安装过程中遇到问题请善用百度 / Google,本人不对此负责(bushi
做好以上所有工作后,就可以开始下面的内容。
Service Worker
安装
不知道是什么、如何部署的,可以看看 CYF 大佬的这两篇文章:
如果你已经部署了 Service Worker 就可以继续了。
脚本
添加一个监听器,监听fetch事件:
self.addEventListener('fetch', async (event) => {
//...
});(或者在本来的监听器里面加上)
然后判断流量是否是对图站的请求,可以用一个 if 来判断:
if (event.request.url.indexOf('your.image.site') !== -1) {
var requestUrl = event.request.url;
//...
}event.request.url是请求的地址,用indexOf()方法来判断地址中是否包含图站地址,若不反回代表没有的 - 1 即为是对图站的请求。
接下来判断浏览器是否支持 webp 图片,定义一个变量supportsWebp
var supportsWebp = false;
if (event.request.headers.has('accept')) {
supportsWebp = event.request.headers.get('accept').includes('webp');
}如果可以获取到浏览器的 Accept 头,且头中包含image/webp,即为支持 webp,否则为不支持。
然后就可以进一步处理了,若浏览器支持 webp,则进行下一步:
if (supportsWebp) {
//...
} else {
console.log("[SW] Don't support webp image, skip " + requestUrl + ' .');
}然后获取请求的文件类型。最开始的脚本只支持png,jpg、jpeg与tiff这四种格式的图片,所以我们也只能篡改这四种格式图片的请求到 webp 图片上:
var imageUrl = requestUrl.split('.');
if (
imageUrl[imageUrl.length - 1] === 'jpg' ||
imageUrl[imageUrl.length - 1] === 'tif' ||
imageUrl[imageUrl.length - 1] === 'png' ||
imageUrl[imageUrl.length - 1] === 'jpeg'
) {
var newUrl = requestUrl.replace(imageUrl[imageUrl.length - 1], 'webp');
//...
}newUrl中存储了新的请求地址,接下来对它发起请求即可:
var newRequest = new Request(newUrl);
event.respondWith(fetch(newRequest));
console.log('[SW] Redirect ' + requestUrl + ' to ' + newUrl + ' .');当请求完成并图片被完整下载以后,进行缓存,代码如下:
event.waitUntil(
fetch(newRequest)
.then(function (response) {
if (!response.ok) throw new Error('[SW] Failed to load image: ' + newUrl);
caches.open(CACHE_NAME).then(function (cache) {
cache.put(newRequest, response);
});
})
.catch(function (error) {
console.log(error);
}),
);若获取失败则提示,成功则缓存。
最后,要打断之前的请求,避免降低速度,可以调用event.stopImmediatePropagation()方法打断原始请求。
最后完整代码如下:
if (event.request.url.indexOf('base.pics.ordchaos.com') !== -1) {
var requestUrl = event.request.url;
var supportsWebp = false;
if (event.request.headers.has('accept')) {
supportsWebp = event.request.headers.get('accept').includes('webp');
}
if (supportsWebp) {
var imageUrl = requestUrl.split('.');
if (
imageUrl[imageUrl.length - 1] === 'jpg' ||
imageUrl[imageUrl.length - 1] === 'tif' ||
imageUrl[imageUrl.length - 1] === 'png' ||
imageUrl[imageUrl.length - 1] === 'jpeg'
) {
var newUrl = requestUrl.replace(imageUrl[imageUrl.length - 1], 'webp');
var newRequest = new Request(newUrl);
event.respondWith(fetch(newRequest));
console.log('[SW] Redirect ' + requestUrl + ' to ' + newUrl + ' .');
event.waitUntil(
fetch(newRequest)
.then(function (response) {
if (!response.ok) throw new Error('[SW] Failed to load image: ' + newUrl);
caches.open(CACHE_NAME).then(function (cache) {
cache.put(newRequest, response);
});
})
.catch(function (error) {
console.log(error);
}),
);
event.stopImmediatePropagation();
return;
}
} else {
console.log("[SW] Don't support webp image, skip " + requestUrl + ' .');
}
}你学会了吗?
测试
进入网站,若一切正常,当加载到一张图片时,控制台(F12打开)会提示[SW] Redirect https://your.image.site/path/to/img.png to https://your.image.site/path/to/img.webp .这样的信息。
要测试无 webp 支持的情景,则点击右上角的三个点。


选择更多工具,找到 “渲染” 并点击。

勾选 “停用 webp” 即可。
此时,加载图片时会提示[SW] Don't support webp image, skip https://your.image.site/path/to/img.png .
可以试试我这里的这张图片:(图片来自 CYF 大佬的Client Worker项目的文档)

若浏览器支持 webp 则会显示Webp Accept!,否则为Webp Reject!This is a jpg file.
(2024.08.03) 现在支持 avif 则会优先显示Avif Accept!
题外话
刚刚放寒假,舒坦。
但与之对应,九上已经结束,还有一学期就中考。。。
加油!我可以的!
那就这样,886!
2024.08.03 更新
看了 Heo 的实现全站图片使用 avif 格式,替代臃肿的 webp 教程,好吧,再换一下。
服务器上使用cavif工具转换图片格式,service worker上简单改一下就好。
参考:
img2webp.sh内容:
#!/bin/bash
convert_to_formats() {
local file="$1"
local base="${file%.*}"
if [ ! -f "${base}.webp" ]; then
cwebp -q 85 "$file" -o "${base}.webp"
fi
if [ ! -f "${base}.avif" ]; then
cavif -Q 80 "$file" -o "${base}.avif"
fi
}
find . -type f -iname "*.png" | while read file; do
convert_to_formats "$file"
done
find . -type f -iname "*.jpg" | while read file; do
convert_to_formats "$file"
done
find . -type f -iname "*.jpeg" | while read file; do
convert_to_formats "$file"
donesw.js重定向段:
if (event.request.url.indexOf('base.pics.ordchaos.com') !== -1) {
var supportsWebp = false;
var supportsAvif = false;
if (event.request.headers.has('accept')) {
var acceptHeader = event.request.headers.get('accept');
supportsWebp = acceptHeader.includes('webp');
supportsAvif = acceptHeader.includes('avif');
}
var imageUrl = requestUrl.split('.');
var fileExtension = imageUrl[imageUrl.length - 1];
if (fileExtension === 'jpg' || fileExtension === 'png' || fileExtension === 'jpeg') {
var newUrl;
if (supportsAvif) {
newUrl = requestUrl.replace(fileExtension, 'avif');
console.log('[SW] Redirect ' + requestUrl + ' to ' + newUrl + ' (AVIF).');
} else if (supportsWebp) {
newUrl = requestUrl.replace(fileExtension, 'webp');
console.log('[SW] Redirect ' + requestUrl + ' to ' + newUrl + ' (WebP).');
} else {
console.log("[SW] Don't support AVIF or WebP, using original format for " + requestUrl + '.');
newUrl = requestUrl;
}
var newRequest = new Request(newUrl);
event.respondWith(
fetch(newRequest)
.then(function (response) {
if (!response.ok) throw new Error('[SW] Failed to load image: ' + newUrl);
return caches.open(CACHE_NAME).then(function (cache) {
cache.put(newRequest, response.clone());
return response;
});
})
.catch(function (error) {
console.log(error);
return fetch(event.request);
}),
);
return;
}
}