然而图片每每是流量大户,与其费尽心机优化脚本体积,可能还不如转换一张大图带来的收益更多。据 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,开启这个功能意味低版本浏览器显示不了图片!
不同格式的图片,纵然像素完备相同,但文件数据显然是不同的。如果业务依赖文件数据,例如校验文件 Hash,那么显然会失落败,从而导致业务破坏。
对付这个问题,有两种缓解方案:
判断 Fetch Metadata 干系的要求头,对付有能力读取文件数据的要求,则不考虑升级通过黑白名单机制,只许可或不许可某些图片升级第 1 种方案更通用,但 Fetch Metadata 只有较高版本的浏览器才支持,并且某些分外场合仍可能存在问题。第 2 种方案更稳定,但需整理文件列表并在后端掩护,显然很麻烦。
3. 前端方案如果网站搭建在虚拟空间、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 供应的。详细事理和细节可 查看这里。
优点前端接入 Service Worker 很大略,只需引用一个脚本即可,无需修正业务逻辑。而后端则无需任何修正,无论是普通的做事器、CDN 还是虚拟空间都可以。
由于 Service Worker 运行在前端,因此能获取到更详细的 要求高下文信息,从而可实现更智能的策略。此外,纵然要配置黑白名单,只需通过一个清单文件即可实现,比修正后端做事配置方便很多。
缺陷如果用户的浏览器不支持脚本,或者根本不是浏览器访问,那么 Service Worker 显然无法运行,图片升级功能自然就失落效了。这种情形只能利用后端方案。
BackDone