前言

WebP 格式发布已有十余年,但不少站点至今仍未使用,只为兼顾极少数低版本浏览器。至于去年发布的 AVIF 格式,使用的站点就更少了。

然而图片往往是流量大户,与其费尽心机优化脚本体积,可能还不如转换一张大图带来的收益更多。据 caniuse 统计,如今有 67% 的用户支持 AVIF、95% 的用户支持 WebP。先进的格式触手可得,却因兼容性问题仍坚守 PNG、GIF 等古老格式,白白浪费网站流量,以及用户加载时间,实在浪费。

事实上,对于同一个图片 URL,完全可为低版本浏览器使用老格式,为高版本浏览器使用新格式,从而实现无缝兼容。本文讲解后端和前端两种不同的实现方案。

后端方案

这是最简单也是最普及的方案,网上能搜到很多相关的文章。不过这其中存在诸多细节,大多数文章都未考虑全面。

原理

支持 WebP 的浏览器,HTTP 请求的 Accept 头会包含 image/webp 字符,后端可根据该特征返回 WebP 版图片;不支持的浏览器则没有该特征,后端返回原始图片。

AVIF 同理,特征为 image/avif。由于 AVIF 比 WebP 更先进,因此需优先判断。

实现

由于图片解码和编码开销很大,因此格式转换通常离线完成,例如预先将 foo.jpg 转换成 foo.jpg.webpfoo.jpg.avif。这里有几个细节:

  • 如果 WebP 文件比原文件更大,那就没有必要保留 WebP 文件。AVIF 同理

  • 如果原文件本身就是 WebP 文件,那就不用再转 WebP 了。但可以尝试转 AVIF 版本,如果更小则保留

  • 如果原文件本身就是 AVIF 文件,那什么都不用做

运行时的判断逻辑很简单,但也容易疏漏。以 nginx 为例,通常会这样配置:

http {
map $http_accept $_ext {
~image/avif .avif;
~image/webp .webp;
default '';
}
server {
location / {
add_header Vary Accept;
try_files $uri$_ext $uri =404;
}
}
}

看起来好像没问题,但遇到这种情况就不对了:用户支持 WebP 和 AVIF,但后端只存在 WebP 文件。正常应该返回 WebP,但这里 try_files 只尝试一次,找不到 $uri.avif 就返回原文件了。显然不对。

正确应该 try_files 两次:

http {
map $http_accept $_avif {
~image/avif .avif;
~image/webp .webp;
default '';
}
map $http_accept $_webp {
~image/webp .webp;
default '';
}
server {
location / {
add_header Vary Accept;
try_files $uri$_avif $uri$_webp $uri =404;
}
}
}

注意这里的逻辑顺序。即使用户不支持 AVIF 扩展名也不能直接返回空,否则就是在尝试原文件了。至于可能会重复尝试两次 WebP 文件,虽不优雅但也无大碍。

此外,如果希望用户访问目录名时 URL 末尾能自动添加 /(例如访问 /blogs 时先重定向到 /blogs/),那么 try_files 还需添加 $uri/

演示

访问:https://www.etherdream.com/img-test/fox.html

支持 AVIF 的浏览器,返回的图片类型为 image/avif

不支持 AVIF 但支持 WebP 的浏览器,返回的图片类型为 image/webp

既不支持 AVIF 又不支持 WebP 的浏览器,返回的图片类型为 image/jpeg

优点

后端实现的方案显然通用性很好,前端无需修改即可生效,甚至前端不是浏览器都没关系,只要遵循 HTTP 的 Accept 规范即可。

缺点 1

由于同一个 URL 会返回不同的内容,如需通过 CDN 加速,则需配置 Vary: Accept 响应头,以确保代理服务器能根据不同的 Accept 请求头缓存相应的内容。然而目前 CloudFlare 免费版却无视 Vary,开启这个功能意味低版本浏览器显示不了图片!

缺点 2

不同格式的图片,即使像素完全相同,但文件数据显然是不同的。假如业务依赖文件数据,例如校验文件 Hash,那么显然会失败,从而导致业务损坏。

对于这个问题,有两种缓解方案:

  1. 判断 Fetch Metadata 相关的请求头,对于有能力读取文件数据的请求,则不考虑升级

  2. 通过黑白名单机制,只允许或不允许某些图片升级

第 1 种方案更通用,但 Fetch Metadata 只有较高版本的浏览器才支持,并且某些特殊场合仍可能存在问题。第 2 种方案更稳定,但需整理文件列表并在后端维护,显然很麻烦。

前端方案

如果网站搭建在虚拟空间、GitHub Pages 等这类无法修改配置的后端,或者使用了 CloudFlare 免费加速服务,那只能在前端实现。

原理

前端升级图片有多种方案。最容易想到的就是用 JS 在线解码高版本图片。当初 WebP 发布时我对此颇有兴趣,尝试用 Flash 实现 WebP 解码器,并且能自动替换网页中的图片元素,看起来就像原生支持一样。但实际应用后发现并不理想,一是不支持 CSS 图片(实现很麻烦),二是解码性能差。虽然使用了 Alchemy 编译技术(LLVM → ActionScript ByteCode),但性能相比原生仍差一大截。最终放弃了这个方案。

尽管后来有更先进的计算方案,例如 WebWorker、asm.js / WebAssembly、SIMD 等,但仍然达不到原生性能,并且代码体积很大。所以在线解码的方案仍不考虑。

直到另一个黑科技的出现,使得前端升级图片变得非常容易,并能覆盖网页中所有图片,那就是 Service Worker。它能拦截当前站点产生的所有请求,并能控制返回结果,相当于一个反向代理服务。于是我们可以在 Service Worker 中判断 Accept 请求头,然后代理到相应的 URL。

实现

得益于 Service Worker 强大的功能,图片除了格式升级外还能玩出很多「骚操作」,例如可将图片部署在免费的图床、相册上,使用时根据清单中的地址进行反向代理,从而可将图片流量降低到 0!并且可准备多个图片 URL 做冗余备份,以及完整性校验等等。这个思路之前在 网站 CDN 去中心化 尝试过,不过实际应用起来似乎并不容易。

最近我重新整理这个思路,并实现了一个工具:freecdn,它可以自动生成清单文件,记录原文件的备用 URL 列表、Hash 值、是否支持 WebP/AVIF 升级等信息。

演示

访问:https://freecdn.etherdream.com/fox.html

Service Worker 不仅将 JPEG 升级成 AVIF 版本,甚至从免费 CDN 加载,将流量开销「优化」到了零!

还有更有趣的现象 —— 新建一个隐身窗口,打开控制台网络栏,访问:https://freecdn.etherdream.com/fox.jpg

从浏览器界面上看,和直接访问图片一模一样,但实际上该图片是由 Service Worker 提供的。具体原理和细节可 查看这里

优点

后端则无需任何修改,无论是普通的服务器、CDN 还是虚拟空间都可以。前端只需引用一个脚本开启 Service Worker,无需修改业务逻辑。

由于 Service Worker 运行在前端,因此能获取到更详细的 请求上下文信息,从而可实现更智能的策略。此外,即使要配置黑白名单,只需通过一个清单文件即可实现,比修改后端服务配置方便很多。

缺点

如果用户的浏览器不支持脚本,或者根本不是浏览器访问,那么 Service Worker 显然无法运行,图片升级功能自然就失效了。这种情况只能使用后端方案。

网站图片无缝兼容 WebP/AVIF的更多相关文章

  1. 网站图片增强JS插件2.0(兼容IE&FF)

    网站图片增强JS插件2.0简单介绍:插件可以增强网站互动能力与外链建设,用户在欣赏图片的同时,把看好的图片直接制作成自己喜欢的样式后通过QQ等传播,增强外链建设,通过用户互动创造外链.(支持:放大缩小 ...

  2. JavaScript学习笔记5 之 计时器 & scroll、offset、client系列属性 & 图片无缝滚动

    一.计时器 setInterval ( 函数/名称 , 毫秒数 )表示每经过一定的毫秒后,执行一次相应的函数(重复) setTimeout ( 函数/名称 , 毫秒数 ) 表示经过一定的毫秒后,只执行 ...

  3. 网站banner无缝轮播

    网站banner无缝轮播 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  4. 微软官方网站线上兼容测试平台-Browser screenshots

    前端开发时最不想做的就是在不同浏览器.平台和分辨率测试网页显示效果,通常这会浮现许多问题,尤其浏览器版本就可能让显示成效完全不同,也只好尽力维持让每一种设备都能正常浏览网页.修改到完全没有问题必须投入 ...

  5. 实现无缝兼容ajax/websocket网页应用和服务

    为了让用户体验更好,页面前端往往是通过ajax来进行数据处理:由于浏览器的设计原因每个域名下的连接有限,这样导致了同时进行ajax数据请求效率无法得到有效地提升,为了提高效率和传统HTTP协议上的限制 ...

  6. 批量下载网站图片的Python实用小工具(下)

    引子 在 批量下载网站图片的Python实用小工具 一文中,讲解了开发一个Python小工具来实现网站图片的并发批量拉取.不过那个工具仅限于特定网站的特定规则,本文将基于其代码实现,开发一个更加通用的 ...

  7. web网站如何实现兼容手机

    web网站如何实现兼容手机 一.总结 一句话总结:加上这句话即可:<meta name="viewport" content="width=device-width ...

  8. C#获取网页的HTML码、下载网站图片、获取IP地址

    1.根据URL请求获取页面HTML代码 /// <summary> /// 获取网页的HTML码 /// </summary> /// <param name=" ...

  9. 批量下载网站图片的Python实用小工具

    定位 本文适合于熟悉Python编程且对互联网高清图片饶有兴趣的筒鞋.读完本文后,将学会如何使用Python库批量并发地抓取网页和下载图片资源.只要懂得如何安装Python库以及运行Python程序, ...

随机推荐

  1. Apple Xcode 12.5 (12E262) 正式版发布 - 构建 Universal App

    请访问原文链接:https://sysin.org/article/apple-xcode-12/,查看最新版.转载请保留出处. Xcode 12 简介 Xcode 12 采用全新设计,在 macOS ...

  2. GO学习-(1) why go?

    为什么你应该学习Go语言? 终于等到你!Go语言--让你用写Python代码的开发效率编写C语言代码. 为什么互联网世界需要Go语言 世界上已经有太多太多的编程语言了,为什么又出来一个Go语言? 硬件 ...

  3. 向量算子优化Vector Operation Optimization

    向量算子优化Vector Operation Optimization 查看MATLAB命令View MATLAB Command 示例显示Simulink编码器 ,将生成向量的块输出,设置为标量,优 ...

  4. H.264 Video Codec速度和质量

    H.264 Video Codec速度和质量 从Kepler开始的所有 NVIDIA GPUs 都支持完全加速的硬件视频编码: GPUs 从费米开始支持完全加速的硬件视频解码.最近发布的图灵硬件提供了 ...

  5. Postman 的基本功能按钮解释、发送post请求及get请求、查看响应信息

    一.界面功能按钮: 二.postman模拟发送post请求: 三.postman模拟get请求: 选择请求类型-->输入请求URL-->点击send发送 四.查看响应信息

  6. Etcd中Raft joint consensus的实现

    Joint consensus 分为2个阶段,first switches to a transitional configuration we call joint consensus; once ...

  7. Redis--狂神说Redis基础汇总(完结)

    Redis--狂神说Redis基础汇总(完结) 2021.6.12-2021.6.14:端午学学玩玩弄完了Redis基础的汇总,越学越觉得自己知识量的匮乏. 参考链接:狂神说Java--Redis汇总 ...

  8. 【Azure 环境】由为存储账号(Storage Account)拒绝分配权限而引出的Azure 蓝图(Blueprint)使用问题

    问题描述 当打开Azure存储账号(Storage Account)门户页面时,从 "访问控制(标识和访问管理)" 页面中发现有"拒绝分配"的功能,所以就思考, ...

  9. 1.5w字 + 24张图肝翻 TCP。

    TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...

  10. DB2 SQL0805N解决和思考

    一.报错现象 这是一个在使用 DB2数据库过程中比较常见的错误, 报错信息如下 Exception stack trace: com.ibm.db2.jcc.am.SqlException: DB2 ...