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渲染机制存在本质差异:
-
<img>标签:浏览器将 SVG 视为静态光栅图像。它没有独立的文档上下文,背景默认透明,渲染逻辑直接依附于父页面。 -
<object>标签:浏览器为 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>