跳到主要内容

SMIL与属性动画

SMIL 在 SVG 里主要是 animate / animateTransform / animateMotion(以及 set)。它写在文档里,不依赖页面 JS 或外部 CSS。Chrome 2015 年对 SMIL 发过弃用意向,2016-08 已暂停;到 2026,Chromium / Gecko / WebKit 仍能跑。生产口径是 仍可用,岗位是自包含文件,不是应用 UI 的默认时间轴。

应用内首选 CSS 与 WAAPI。SMIL 的岗位是自包含 .svg 文件。

1. 适用 / 不适用

适用: 作为 <img src="*.svg">、CSS background-image、无运行时的静态资源;需要 animateMotion 沿 path,而文件里不许带 JS。

不适用: 要暂停、seek、和按钮状态机同步的产品 UI;要进用户上传 / 富文本的任意 SVG(扩大可执行面);要三引擎像素级对拍的复杂 syncbase 时间图。引擎边角见 SMIL、foreignObject 与文本

2. 自包含文件怎么写

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<circle cx="32" cy="32" r="8" fill="steelblue">
<animate
attributeName="r"
values="8;12;8"
dur="1.2s"
repeatCount="indefinite"
/>
</circle>
</svg>

<img> 不执行脚本,但 会跑文件内 SMIL——这正是 2016 暂停弃用的理由之一:CSS / WAAPI 进不去这层沙箱。attributeName 动的是 XML 属性,不依赖「该属性是否已升级成 CSS 几何属性」。

animateTransformtransform 属性,和页面 CSS transform 会抢。自包含文件里不要混用。animateMotion路径动画

3. begin / end 事件与时间图

<rect id="a" x="0" y="0" width="20" height="20">
<animate id="go" attributeName="x" from="0" to="40" dur="0.4s" begin="indefinite" fill="freeze"/>
</rect>
export function startSmil(anim: SVGAnimationElement): void {
anim.beginElement();
}

export function onSmilEnd(
anim: SVGAnimationElement,
handler: () => void,
): () => void {
const wrapped = (): void => {
handler();
};
anim.addEventListener("endEvent", wrapped);
return () => {
anim.removeEventListener("endEvent", wrapped);
};
}

规范事件是 beginEvent / endEvent / repeatEvent(注意不是 HTML 的 begin)。begin="go.end+0.2s" 这种 syncbase 在 Gecko 上最完整,Chromium 有历史边角,不要当跨引擎编排总线。

begin="click" / mouseover 让「静态图」变成可交互时间线。自包含装饰用时钟 0s;交互应发生在 有 JS 的 inline 文档 里,用 WAAPI。

4. 用户上传:SMIL 按可执行面处理

用户 SVG 的威胁模型和 HTML 同类,见 08 安全。消毒如果只剥 &lt;script>on*,留下 animate* 等于留下:

  • 事件 begin(点击、焦点)驱动的时间线
  • 与漏网 script / foreignObject 的组合面
  • 无限 repeatCount 的 CPU / 电池消耗

产品规则:用户上传白名单不要包含 SMIL 元素。 illustrator / 设计稿进编辑器,先烘焙成静态几何,动画由应用自己的 WAAPI 加。不要「看起来只是图,就 inline」。

const SMIL_TAGS = new Set([
"animate",
"animateTransform",
"animateMotion",
"animateColor",
"set",
]);

export function rejectSmil(root: SVGElement): boolean {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node: Node | null = walker.currentNode;
while (node) {
if (node instanceof SVGElement && SMIL_TAGS.has(node.localName)) {
return true;
}
node = walker.nextNode();
}
return false;
}

发现即拒收或剥掉整棵动画子树,不要尝试「安全地解释」用户时间线。

5. 失败形态

症状原因
文档写了 CSS 关键帧,<img> 里不动SMIL 才是无 JS 通道;页面 CSS 进不去 img 文档
点一下才播、消毒后仍能播begin="click" 等事件 begin 没剥
Chrome / Firefox 衔接差 1 拍syncbase / repeatCount 细节当可移植协议用了
和 WAAPI 互相打架同一 transform 两条写入通道
上传 SVG 吃满 CPU未拦截 indefinite SMIL

权威资料

核对日期:2026-08-26