Skip to content

解决 SVG 动画规范问题 ​

为图标添加动画有多种方式:

  • 使用关键帧的 CSS 动画。
  • JavaScript 动画。有多个库可用于实现此功能。
  • SVG 动画。

本文主要介绍最后一种方法:使用 SVG 动画 2 级规范 为图标添加动画。

SMIL? ​

不,它不是那个已被废弃的旧版 SMIL 动画。

它是一个现代规范,尽管基于 SMIL。目前所有现代浏览器都已支持它。

示例 ​

那么 SVG 动画看起来是什么样的呢?

svg<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
   <g stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
       <path d="M3 14V5H21V19H3V14" fill="none" stroke-width="2" stroke-dasharray="66" stroke-dashoffset="66">
           <animate attributeName="stroke-dashoffset" values="66;0" dur="0.6s" fill="freeze" />
       </path>
       <path d="M3 16L7 13L10 15L16 10L21 14V19H3Z" fill="currentColor" fill-opacity="0" stroke-dasharray="52" stroke-dashoffset="52">
           <animate attributeName="stroke-dashoffset" values="52;0" begin="0.6s" dur="0.8s" fill="freeze" />
           <animate attributeName="fill-opacity" values="0;0.3" begin="1s" dur="0.15s" fill="freeze" />
       </path>
   </g>
   <circle cx="7.5" cy="9.5" r="1.5" fill="currentColor" fill-opacity="0">
       <animate attributeName="fill-opacity" values="0;1" begin="1s" dur="0.4s" fill="freeze" />
   </circle>
</svg>

此示例使用 <animate> 标签随时间更改父元素的属性。它通过更改 stroke-dashoffset 来为描边添加动画,使其看起来像是图标正在被绘制,并通过 fill-opacity 让填充元素淡入。

无需 CSS,无需 JavaScript,无外部依赖。

有关更多示例以及其他相关标签和属性,请参阅 MDN 上的 <animate> 文档 和 SVG 动画 2 级规范。

时序控制 ​

遗憾的是,SVG 动画规范有一个主要缺点:动画时序的处理非常棘手。

这究竟意味着什么?

渲染 SVG 有两种方式:

  • 使用 <svg> 内联图标。
  • 外部资源:将 SVG 作为 URL 或背景图像。

两者在动画时序方面都存在不同的问题。

内联 SVG ​

使用内联 <svg> 时,动画必须等到文档准备就绪后才能开始。

这看起来可能微不足道,但动画渲染不够快会导致糟糕的用户体验。

此问题可能由一些小事引起,例如统计脚本加载失败或广告代码加载缓慢。如果页面加载资源的服务器中至少有一个无法访问,就可能会破坏页面上所有带动画的 SVG。async 和 defer 属性也无济于事。该问题甚至可能由 iframe 中的脚本引起。

解决方案? ​

没有。

如果您的网站有大量可能延迟动画的外部依赖项,请不要对内联 SVG 使用带动画的图标。

将 SVG 作为 URL ​

当将图标作为 URL 使用时,您根本无法控制动画时序。

动画总是在图标首次渲染时开始。绝对没有办法控制这一点。

解决方案? ​

唯一的解决方案是每次需要重新启动动画时,为图标生成唯一的 URL。

实现这一点的最简单方法是在图标内容中添加一个基本的 HTML 注释,如下所示:

css.test-icon {
 --svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cg fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'%3E%3Cpath stroke-dasharray='20' stroke-dashoffset='20' d='M3 12h17.5'%3E%3Canimate fill='freeze' attributeName='stroke-dashoffset' dur='0.2s' values='20;0' /%3E%3C/path%3E%3Cpath stroke-dasharray='12' stroke-dashoffset='12' d='M21 12l-7 7M21 12l-7 -7'%3E%3Canimate fill='freeze' attributeName='stroke-dashoffset' begin='0.2s' dur='0.2s' values='12;0' /%3E%3C/path%3E%3C/g%3E%3C!-- 1234567890 --%3E%3C/svg%3E");
 width: 1em;
 height: 1em;
 background-color: currentcolor;
 mask-image: var(--svg);
 mask-repeat: no-repeat;
 mask-size: 100% 100%;
}

注意 URL 末尾附近的 "%3C!-- 1234567890 --%3E"。这是一个带有数字的简单注释,用于破坏浏览器的缓存。

遗憾的是,此解决方案需要 JavaScript。它可以用作内联 CSS,但不能用于 CSS 文件中。

Iconify Web 组件 ​

解决这些问题的一个简单方法是使用 Iconify 图标 Web 组件。

Iconify 图标 Web 组件 将带动画的图标渲染为背景或遮罩图像,并实现了随机 URL 的变通方案。

此外,它仅在图标可见时才渲染图标。这意味着 CPU 不会浪费在为不可见的图标添加动画上。

结论 ​

SVG 动画规范前景广阔,但使用起来很困难。

CSS 动画是更好的选择,但在撰写本文时,并非所有主流浏览器都完全支持 CSS 动画(见下文更新)。更具体地说,只有像 stroke-dashoffset 这样的基本动画有效,不支持为其他属性添加动画(见下文更新)。这使得在 CSS 中为图标添加动画除了非常基础的动画外几乎不可能实现。

更新 ​

CSS 动画更新:2025 年 4 月。

2024 年,所有主流浏览器都增加了对在 CSS 中为大多数 SVG 属性添加动画的支持。

唯一的例外是路径中的 d 属性,这实际上是最重要的属性。Safari 18.4 不支持在 CSS 中为 d 添加动画或更改它,但 Safari TP 已支持,因此希望该支持能随 Safari 18.5 一同发布。