跳到主要内容
2023-01-12
6 分钟
...
教程

文章摘要

这篇博文介绍了博主通过生成WebP格式图片并利用Service Worker实现原图与WebP的自适应切换,以提升博客图片加载性能的实践过程。

原本博客用的都是普通图片,就算有懒加载,一堆圈圈一起转也惹人心烦。现在改为了原图 /webp 的自适应切换,效果好上不少。

前期准备

首要任务是拿到 webp 格式的图片,这个看自己。像我用的 vps 上的Lsky Pro,本地存储。有高性能 vps 可以试试用Webp-Server配合。但我的轻量应用承受不起,遂作罢。改为了定时 shell 脚本,一分钟触发一次:

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 格式的图片(pngjpgjpegtiff)为 webp 图片(原图还在,放心)。

这段脚本中使用了cwebp指令,它来源于libwebp。安装可以参考下方:

SHELL
# 安装编译器以及依赖包
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事件:

JAVASCRIPT
self.addEventListener('fetch', async (event) => {
  //...
});

(或者在本来的监听器里面加上)

然后判断流量是否是对图站的请求,可以用一个 if 来判断:

JAVASCRIPT
if (event.request.url.indexOf('your.image.site') !== -1) {
  var requestUrl = event.request.url;
  //...
}

event.request.url是请求的地址,用indexOf()方法来判断地址中是否包含图站地址,若不反回代表没有的 - 1 即为是对图站的请求。

接下来判断浏览器是否支持 webp 图片,定义一个变量supportsWebp

JAVASCRIPT
var supportsWebp = false;
if (event.request.headers.has('accept')) {
  supportsWebp = event.request.headers.get('accept').includes('webp');
}

如果可以获取到浏览器的 Accept 头,且头中包含image/webp,即为支持 webp,否则为不支持。

然后就可以进一步处理了,若浏览器支持 webp,则进行下一步:

JAVASCRIPT
if (supportsWebp) {
  //...
} else {
  console.log("[SW] Don't support webp image, skip " + requestUrl + ' .');
}

然后获取请求的文件类型。最开始的脚本只支持pngjpgjpegtiff这四种格式的图片,所以我们也只能篡改这四种格式图片的请求到 webp 图片上:

JAVASCRIPT
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中存储了新的请求地址,接下来对它发起请求即可:

JAVASCRIPT
var newRequest = new Request(newUrl);
event.respondWith(fetch(newRequest));
console.log('[SW] Redirect ' + requestUrl + ' to ' + newUrl + ' .');

当请求完成并图片被完整下载以后,进行缓存,代码如下:

JAVASCRIPT
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()方法打断原始请求。

最后完整代码如下:

JAVASCRIPT
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内容:

SHELL
#!/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"
done

sw.js重定向段:

JAVASCRIPT
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;
  }
}
全站 webp 自动切换,加速访问好帮手
作者
序炁
发布于
2023-01-12
许可协议
BY - 署名NC - 非商业性使用SA - 相同方式共享
QQ空间微博豆瓣XFacebookLinkedIn复制链接