Object 引用 SVG 深色模式踩坑

问题背景

之前受到 GitHub 自动切换 favicon 的效果启发,我为个人网站设计了自适应深色模式的 SVG Logo,通过内部媒体查询实现浅色/深色的动态切换:

@media (prefers-color-scheme: light) { #logo { fill: #403f5f; } }
@media (prefers-color-scheme: dark) { #logo { fill: #ffffff; } }

在网页首页通过 <img> 标签引入 Logo 时,发现 Safari 不支持渲染通过 <img> 标签嵌入的外部 SVG 文件,搜索发现需要改用 <object> 标签引入:

<object type="image/svg+xml" data="/favicon.svg"></object>

改用 <object> 后,虽然 Safari 能渲染 SVG 了,但只在浅色模式下正常,系统开启深色模式后,通过 <object> 标签引用的 Logo 在各浏览器都变成了一个纯白方块,图形轮廓消失。当时由于不知如何定位成因,只好暂时不了了之。两年后,我在制作其他小工具时,终于重新发现了导致这个问题的原因。

成因分析

这个bug的出现,是由于浏览器对于这两种标签下的SVG渲染机制存在本质差异:

浏览器在处理通过 <object> 标签嵌入的 SVG 图形时,父页面与子文档(SVG)的 color-scheme: light dark; 声明是否匹配,将直接影响深色模式下 SVG 中透明区域的渲染效果:

父页面声明 SVG 声明 浏览器逻辑 SVG透明区域的渲染效果
场景一 未声明 未声明 双方皆未知,浏览器不干预,Canvas 保持透明。 透明
场景二 已声明 已声明 认为作者已协调好方案,撤销干预。 透明
场景三 已声明 未声明 认为子文档不支持暗色,强制垫白底。 纯白
场景四 未声明 已声明 按标准应用系统暗色默认背景。 纯黑

我的网站 Logo 默认藏蓝色,暗色模式下,通过内部的媒体查询规则将 Logo 反转为白色。网站做了 color-scheme 声明而 SVG 文件本身没有声明(满足场景三),于是浏览器强制为 SVG 的透明部分添加白色背景,白上加白,原本挖空的白色 LOGO 就变成实心的白色方块了。

解决方法

修复的关键在于消除网页与 SVG 子文档之间的颜色方案信息差。不仅要在网页(HTML 根元素或父级元素)声明 color-scheme: light dark; , SVG 也必须在其根元素显式声明对等的 color-scheme。只有当两者的口径完全一致时,浏览器才既不会为了安全强制给透明区域垫白底,也不会为了符合暗色规范强制垫黑底,让 <object> 的画布真正回归最原始的透明状态。

<svg style="color-scheme: light dark;">
  /* 原本图形代码 */
</svg>