网站图片无缝兼容 WebP/AVIF
前言
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.webp
和 foo.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,那么显然会失败,从而导致业务损坏。
对于这个问题,有两种缓解方案:
判断 Fetch Metadata 相关的请求头,对于有能力读取文件数据的请求,则不考虑升级
通过黑白名单机制,只允许或不允许某些图片升级
第 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的更多相关文章
- 网站图片增强JS插件2.0(兼容IE&FF)
网站图片增强JS插件2.0简单介绍:插件可以增强网站互动能力与外链建设,用户在欣赏图片的同时,把看好的图片直接制作成自己喜欢的样式后通过QQ等传播,增强外链建设,通过用户互动创造外链.(支持:放大缩小 ...
- JavaScript学习笔记5 之 计时器 & scroll、offset、client系列属性 & 图片无缝滚动
一.计时器 setInterval ( 函数/名称 , 毫秒数 )表示每经过一定的毫秒后,执行一次相应的函数(重复) setTimeout ( 函数/名称 , 毫秒数 ) 表示经过一定的毫秒后,只执行 ...
- 网站banner无缝轮播
网站banner无缝轮播 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- 微软官方网站线上兼容测试平台-Browser screenshots
前端开发时最不想做的就是在不同浏览器.平台和分辨率测试网页显示效果,通常这会浮现许多问题,尤其浏览器版本就可能让显示成效完全不同,也只好尽力维持让每一种设备都能正常浏览网页.修改到完全没有问题必须投入 ...
- 实现无缝兼容ajax/websocket网页应用和服务
为了让用户体验更好,页面前端往往是通过ajax来进行数据处理:由于浏览器的设计原因每个域名下的连接有限,这样导致了同时进行ajax数据请求效率无法得到有效地提升,为了提高效率和传统HTTP协议上的限制 ...
- 批量下载网站图片的Python实用小工具(下)
引子 在 批量下载网站图片的Python实用小工具 一文中,讲解了开发一个Python小工具来实现网站图片的并发批量拉取.不过那个工具仅限于特定网站的特定规则,本文将基于其代码实现,开发一个更加通用的 ...
- web网站如何实现兼容手机
web网站如何实现兼容手机 一.总结 一句话总结:加上这句话即可:<meta name="viewport" content="width=device-width ...
- C#获取网页的HTML码、下载网站图片、获取IP地址
1.根据URL请求获取页面HTML代码 /// <summary> /// 获取网页的HTML码 /// </summary> /// <param name=" ...
- 批量下载网站图片的Python实用小工具
定位 本文适合于熟悉Python编程且对互联网高清图片饶有兴趣的筒鞋.读完本文后,将学会如何使用Python库批量并发地抓取网页和下载图片资源.只要懂得如何安装Python库以及运行Python程序, ...
随机推荐
- Ubuntu 16.04安装PyCharm
PyCharm一个是Python集成开发环境,它既提供收费的专业版,也提供免费的社区版本.PyCharm带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试.语法高亮.Proj ...
- Mybatis3详解(十四)----Mybatis的分页
1.前言 在前面学习mybatis的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,但是前面都是简单的案例,所以查询的数据量不是很大,自然查询时没有任何压力,但是如果在实际的项目 ...
- SOLO: 按位置分割对象
SOLO: 按位置分割对象 SOLO: Segmenting Objectsby Locations 论文链接: https://arxiv.org/pdf/1912.04488.pdf 代码链接: ...
- YOLOv4没交棒,但YOLOv5来了!
YOLOv4没交棒,但YOLOv5来了! 前言 4月24日,YOLOv4来了! 5月30日,"YOLOv5"来了! 这里的 "YOLOv5" 是带有引号的,因为 ...
- python_request 接口测试线性框架,模块化思想,增加日志打印
一.大框架 如下为一个简单的线性框架,同时编写common_api 模块,把一个个接口进行封装,案例编写时候只需要直接调用输入参数即可. 二. test_cases 模块具体写法 2.1 commo ...
- 最全JVM知识点思维导图,看这一篇就够了
此处是转发别人的,别人花了二个月, 我花一天时间看完, 觉得很有用 https://www.processon.com/view/link/5eea141cf346fb1ae56a44e7
- 【NX二次开发】Block UI 绘图区
属性说明 常规 类型 描述 BlockID String 控件ID Enable Logical 是否可操作 Group Logical ...
- 【VBA】查找字符串
老婆饼里有老婆吗 Sub test() aaa = "老婆饼里有老婆吗" If InStr(aaa, "老婆") <> 0 Then Debug.p ...
- 干货!MySQL 的 InnoDB 存储引擎是怎么设计的?
MySQL 里还有什么其他成员呢? 对于 MySQL,要记住.或者要放在你随时可以找到的地方的两张图,一张是 MySQL 架构图,另一张则是 InnoDB 架构图: 遇到问题,或者学习到新知识点时,就 ...
- 使用 flux2+kustomize+helm+github 进行多集群 GitOps 云原生渐进式交付
对于此示例,我们假设有两个集群的场景:暂存(staging)和生产(production). 最终目标是利用 Flux 和 Kustomize 来管理两个集群,同时最大限度地减少重复声明. 我们将配置 ...