网站图片无缝兼容 WebP/AVIF - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
myqoo
V2EX    分享创造

网站图片无缝兼容 WebP/AVIF

  •  1
     
  •   myqoo 2021 年 7 月 21 日 3066 次点击
    这是一个创建于 1699 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    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 在线解码高版本图片,但性能相比原生仍有差距,并且难以覆盖 CSS 中的图片。

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

    实现

    得益于 Service Worker 强大的功能,图片除了格式升级外还能玩出很多「骚操作」,例如可将图片部署在免费的图床、相册上,使用时根据清单中的地址进行反向代理,从而可将图片流量降低到 0 !并且可准备多个图片 URL 做冗余备份,以及完整性校验等等。

    最近重新整理这个思路,并实现了一个工具: https://github.com/EtherDream/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 显然无法运行,图片升级功能自然就失效了。这种情况只能使用后端方案。

    6 条回复    2022-01-07 18:47:56 +08:00
    yemoluo
        1
    yemoluo  
       2021 年 7 月 22 日
    问一个问题,后端的 avif 和 webp 或者 apng 是怎么生成的?
    myqoo
        2
    myqoo  
    OP
       2021 年 7 月 22 日
    @GTim 写个脚本遍历所有图片文件,再调命令生成就可以。
    powerfj
        3
    powerfj  
       2021 年 7 月 23 日
    赞一下, 思路不错.
    jingslunt
        4
    jingslunt  
       2021 年 7 月 23 日
    AVIF 真的小
    no1xsyzy
        5
    no1xsyzy  
       2021 年 7 月 24 日   1
    MrBrother
        6
    MrBrother  
       2022 年 1 月 7 日 via iPhone
    @no1xsyzy

    感谢,试了一下后发现挺有用
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3542 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 04:55 PVG 12:55 LAX 21:55 JFK 00:55
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86