前言

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. Spring Cloud Alibaba系列之分布式服务组件Dubbo

    本博客的例子代码可以在github找到下载链接:代码下载 SpringBoot.SpringCloud Alibaba系列博客专栏:链接 1.分布式理论 1.1.分布式基本定义 <分布式系统原理 ...

  2. idea配置javaweb项目(最新版)

    idea(最新版)配置javaweb项目 本篇文章使用Maven构建javaweb环境 最新版maven压缩包 链接:https://pan.baidu.com/s/1El7b3YzPTZX-7QRE ...

  3. tomcat---starup.bat点击窗口自动关闭

  4. [leetcode] 48. 旋转图像(Java)(模拟)

    48. 旋转图像 模拟题,其实挺不喜欢做模拟题的... 其实这题一层一层的转就好了,外层转完里层再转,其实就是可重叠的子问题了. 转的时候呢,一个数一个数的转,一个数带动四个数.如图所示,2这个数应该 ...

  5. GO语言常用标准库01---strings包

    package main import ( "fmt" "strings" ) func main031() { fmt.Printf("字符形式:% ...

  6. CVD和ALD薄膜沉积技术应用领域

    CVD和ALD薄膜沉积技术应用领域 显示 用于OLED.QD-OLED.甚至未来QLED的薄膜封装,通过有机/无机叠层结构的保护,水汽渗透率WVTR可降至10-5g/m2/day,保证OLED或者量子 ...

  7. AlexeyAB DarkNet YOLOv3框架解析与应用实践(二)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(二) 版本3有什么新功能? YOLOv3使用了一些技巧来改进训练和提高性能,包括:多尺度预测.更好的主干分类器等等.全部细节都在我们的 ...

  8. MAML-Tracker: 目标跟踪分析:CVPR 2020(Oral)

    MAML-Tracker: 目标跟踪分析:CVPR 2020(Oral) Tracking by Instance Detection: A Meta-Learning Approach 论文链接:h ...

  9. Redis系列(一):安装

    本系列介绍Redis,从安装到使用,太简单的使用不介绍了,介绍一些比较有意思的功能,也会介绍一些原理性的东西.本篇先介绍Redis的单实例安装.Redis还可以做到高可用,通过哨兵和集群可以做到高可用 ...

  10. 【C++】秒级时间戳,毫秒级时间戳

    时间戳,秒级 测试代码: #include <iostream> #include <time.h> #include <windows.h> using name ...