跳到主要内容

SMIL-foreignObject与文本

清单见 对照表。这三项单独展开,因为失败看起来像「偶发空白 / 偶发折行 / 偶发动画不接」,根因却是引擎对 可执行子树、HTML 宿主、SVG 1.1 文字模型 的立场不同。

1. SMIL:能跑 ≠ 当应用协议

机制: 时间图写在文档里,改 XML 属性。<img> 沙箱能跑 SMIL、不能跑 JS。Chromium 2015 弃用意向、2016 暂停;2026 三引擎仍可用。应用 UI 仍走 CSS / WAAPI;SMIL 留给自包含文件。

适用: 无运行时的循环装饰、animateMotion 跟点、邮件/CDN 上的纯文件。

不适用: 把 syncbase 当跨引擎编排;用户上传;和 CSS transform 双写。

失败形态:

症状原因
第二段动画差一拍或偶发不接begin="a.end" 在 Chromium 的边角,Gecko 看起来正常
页面里 WAAPI 把节点转走了,SMIL 又写回两条 transform 通道
消毒后「图片」仍会动、还能响应点击只剥了 script,留下 animate* 和事件 begin
fill="freeze" 导出后位置不对冻结的是动画实例,序列化仍是原属性
export function prefersSmilFile(asImage: boolean, hasJsRuntime: boolean): boolean {
return asImage && !hasJsRuntime;
}

beginElement() 只在 能执行脚本的文档(inline / 同源 object)有意义;<img> 里没有你的监听器。事件名是 endEvent 不是 end

2. foreignObject:HTML 进矢量树

机制: 在用户空间里开一个矩形视口,里面排 HTML。宽高是 SVG 用户单位,不是「随便一个 div」。独立 .svg 文件里的 HTML 要用 XHTML 命名空间。

适用: 必须用 CSS 折行、表单、真正的 HTML 按钮,且宿主是 inline SVG

不适用: &lt;use> 模板里嵌 FO(Chromium / WebKit 直接剥掉);<img> 里当可交互控件;用户上传;打印/Illustrator 往返。

export function mountHtmlBox(
parent: SVGElement,
box: { x: number; y: number; width: number; height: number },
html: HTMLElement,
): SVGForeignObjectElement {
const ns = "http://www.w3.org/2000/svg";
const fo = parent.ownerDocument.createElementNS(ns, "foreignObject");
fo.setAttribute("x", String(box.x));
fo.setAttribute("y", String(box.y));
fo.setAttribute("width", String(box.width));
fo.setAttribute("height", String(box.height));
html.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
fo.append(html);
parent.append(fo);
return fo;
}

属性 width/height 必写。只靠 CSS 在 Gecko / WebKit 上容易得到 0 盒。FO 上再叠 filter / 旋转,裁切和命中是重灾区:overlay 用 getScreenCTM,不要信 FO 内部 offsetX

失败形态:

症状原因
Chrome / Safari 里 use 出来的卡片没 HTMLFO 在 &lt;use> 影子树被剥;Gecko 可能还在,造成「只有 Firefox 正常」
独立文件全空白缺 XHTML xmlns,或 FO 无宽高属性
滚动/裁切和矩形对不上overflow + 变换 + 引擎按位图层合成 FO
上传 SVG 弹页FO 里的脚本/表单,按 HTML XSS 处理

3. 文本:没有浏览器折行

机制: SVG 1.1 的 &lt;text>字形定位列表,不是块级盒。SVG 2 的 inline-size 在浏览器里 三引擎都未实现(设计工具另说)。Gecko 对 white-space: pre + 源码换行会画出多行,这是 Gecko 行为,不是可移植 SVG 2。

适用: 短标签、自己算行的 tspan;长文案用 HTML(inline overlay 或 FO,接受代价)。

不适用: 把 Illustrator / Inkscape 的 SVG 2 自动折行直接丢进浏览器当响应式排版。

export function layoutTspans(
text: SVGTextElement,
lines: readonly string[],
fontSize: number,
lineHeight = 1.25,
): void {
text.replaceChildren();
lines.forEach((line, i) => {
const tspan = text.ownerDocument.createElementNS(
"http://www.w3.org/2000/svg",
"tspan",
);
tspan.setAttribute("x", text.getAttribute("x") ?? "0");
tspan.setAttribute("dy", i === 0 ? "0" : String(fontSize * lineHeight));
tspan.textContent = line;
text.append(tspan);
});
}

行宽用 getComputedTextLength() 自己断行。中英混排、fallback 字体,三引擎宽度不同,不要当视觉回归金标。textPath 更不要指望自动折行。

失败形态:

症状原因
Chrome 一行溢出,Firefox 折了依赖 Gecko white-space: pre
Inkscape 文件在浏览器摊成一条inline-size 被忽略
FO 里 HTML 折行很好,导出丢了编辑器/打印不理解 FO
对齐基线跳dominant-baseline / 字体 metrics 引擎差

4. 怎么选通道

需求通道
应用内动效CSS / WAAPI
纯文件动效SMIL
必须 HTML 排版inline overlay 优先于 FO;FO 不要进 use
必须矢量文字可导出自己 tspan,接受无自动折行

权威资料

核对日期:2026-08-26