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。
不适用: <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 出来的卡片没 HTML | FO 在 <use> 影子树被剥;Gecko 可能还在,造成「只有 Firefox 正常」 |
| 独立文件全空白 | 缺 XHTML xmlns,或 FO 无宽高属性 |
| 滚动/裁切和矩形对不上 | overflow + 变换 + 引擎按位图层合成 FO |
| 上传 SVG 弹页 | FO 里的脚本/表单,按 HTML XSS 处理 |
3. 文本:没有浏览器折行
机制: SVG 1.1 的 <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,接受无自动折行 |
权威资料
- SVG 1.1 — Animation
- SVG 2 — The foreignObject element
- SVG 2 — Text(inline-size 仍待浏览器)
- WebKit Bug 43911 — foreignObject 在 use 中被剥
- svgwg #1087 — inline-size
核对日期:2026-08-26