用 img 标签引用的 SVG,和把同一个 SVG 粘贴进 HTML,并不等价。它们的差异决定了样式、缓存和安全性。
内联 SVG
标记直接存在于你的文档里。
能做到:
- 被页面 CSS 控制样式——fill、stroke、继承 currentColor、悬停态、深色模式变体。
- 对局部做动画、响应 JavaScript、通过 title 和 aria 属性进入无障碍树。
- 完全省掉一次网络请求。
代价:
- 会给每个包含它的页面增加体积,而且不会被单独缓存——同一个图标出现在十个页面上,就随每份 HTML 重新下载十次。
- 如果直接粘贴带着元数据、注释和隐藏图层的原始导出文件,标记会严重膨胀。
- 它的 id 和 class 位于页面的全局命名空间,两个内部 id 相同的内联 SVG 会互相冲突,破坏渐变或裁剪路径。
通过 img(或 CSS 背景)引用 SVG
浏览器把它当作图片,在隔离的上下文中渲染。
能做到:
- 作为独立文件被缓存并跨页面共享——全站通用的 logo 就该这样。
- 保持 HTML 干净。
- 使用常规图片属性:alt 文本、loading、尺寸。
做不到:
- 被页面 CSS 控制样式。文档的规则不会穿透到图片内部,所以你无法用 currentColor 给它改色。(SVG 文件*内部*的样式仍然生效,CSS 变量同样穿不进去。)
- 执行脚本——如下所述,这是特性而非限制。
安全要点
SVG 是 XML,可以包含 script 元素和事件处理器。一旦内联进你的页面,这些脚本就以你的源运行,能完全访问你的 DOM、Cookie 和会话。这让"内联的、用户上传的 SVG"成为一个存储型跨站脚本攻击向量。
规则:
- 绝不内联来源不可信的 SVG。
- 如果接受 SVG 上传,就在服务端用专门的库净化它,剥离脚本、事件处理器、外部引用和 foreignObject。
- 优先通过 img 标签、或从一个独立的无 Cookie 域名分发用户上传的 SVG,让脚本执行被阻止或无害化。
- 如果不需要矢量保真度,考虑把用户 SVG 栅格化为 PNG/WebP——这能整类风险一并消除。
如何选择
- 需要改色和做动画的界面图标 → 内联 SVG(或用 use 引用 SVG 雪碧图,这样能找回缓存)。
- 全站重复出现、无需动态样式的站点 logo → img 引用外部 SVG 文件。
- 装饰性背景图形 → CSS 背景。
- 任何用户上传的内容 → 先净化,再用 img——绝不内联。
无论选哪种,先优化文件:去掉编辑器元数据、降低坐标精度、删除隐藏图层。优化良好的界面图标通常只有几百字节,而未经优化的导出文件动辄十倍于此。