你有没有见过这样的页面:一个加载图标在不停旋转,一个按钮在悬停时优雅地变色,一个图表的数据在页面加载时缓缓“绘制”出来?这些让人眼前一亮的动态效果,背后很大一部分功臣就是 SVG 动画。

SVG 和 GIF 或视频不同——它不需要几十 MB 的文件,也不需要复杂的视频编码。你只需要几行代码,就能让一个矢量图形“活”起来。而且,SVG 是矢量的,无论放大多少倍都清晰如初,这简直是做动画的“天选之子”。

一、为什么 SVG 天生适合做动画?

在深入具体技术之前,先理解一个根本问题:为什么 SVG 做动画比位图(如 PNG、JPG)要轻松得多?

位图由固定数量的像素点组成,改变一个像素的颜色容易,但要改变图形的形状——比如把一个圆变成方——几乎是不可能的。而 SVG 图形本质上是一段代码:

  • <circle cx="50" cy="50" r="40" /> 定义了一个正圆。

  • <rect x="10" y="10" width="80" height="80" /> 定义了一个矩形。

动画的本质就是属性的连续变化——位置从 A 到 B、颜色从蓝到红、半径从 40 到 80。对于 SVG 来说,这些变化就是修改代码中的参数。浏览器可以非常平滑地完成这个过程,而且全程在 GPU 加速下运行,性能极佳。

所以,SVG 做动画不是“能不能”的问题,而是“用什么方法”的问题。

二、方法一:CSS 动画 —— 简单直接的“快车道”

如果你已经熟悉 CSS 动画(transition 和 @keyframes),那么恭喜你——你已经会了 SVG 动画的一半。CSS 动画是让 SVG 动起来最直接、最易上手的方式。

2.1 用 transition 做交互式动画

transition 最适合“状态切换”类的动画——比如鼠标悬停、点击等交互触发的变化。

示例:鼠标悬停

<svg viewBox="0 0 100 100" width="100" height="100">
  <circle cx="50" cy="50" r="40" fill="#3498db" class="hover-circle" />
</svg>

<style>
  .hover-circle {
    transition: all 0.4s ease;
    cursor: pointer;
  }

  .hover-circle:hover {
    fill: #e74c3c;
    transform: scale(1.2);
    transform-origin: center;
  }
</style>

悬停时,圆形会变红并放大 1.2 倍,整个过程平滑流畅。

适用场景:按钮悬停效果、图标状态切换、菜单展开/收起等交互式反馈。

2.2 用 @keyframes 做连续动画

@keyframes 适合“自动播放、持续运行”的动画——比如加载指示器、呼吸效果、装饰性动效。

<svg viewBox="0 0 100 100" width="100" height="100">
  <circle 
    cx="50" cy="50" r="30" 
    fill="none" 
    stroke="#3498db" 
    stroke-width="6" 
    stroke-linecap="round"
    stroke-dasharray="140 48.5"
    class="spinner" 
  />
</svg>

<style>
.spinner {
  animation: spin 1.5s linear infinite;
  transform-origin: center;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}
</style>

这段代码实现了一个经典的加载旋转动画。注意 transform-origin: center 是关键——没有它,旋转会围绕默认的原点 (0,0) 进行,图形会“飞”出画布。

适用场景:加载指示器、呼吸动画、闪烁提示、循环装饰动效。

2.3 CSS 动画的注意事项
  • 颜色和变换(fill、stroke、transform、opacity)是最适合用 CSS 动画的属性,浏览器会对它们做 GPU 加速。

  • 路径变形(如把圆变成方的 morphing 效果)用 CSS 很难实现,需要用到 SMIL 或 JavaScript。

  • 如果动画需要精确的时间轴控制或 复杂的序列,CSS 也会显得力不从心。

三、方法二:SMIL 动画 —— SVG 自带的“原生技能”

SMIL(Synchronized Multimedia Integration Language,发音类似“smile”)是 SVG 规范中原生自带的动画语法。你不需要引入任何额外文件,也不需要写 CSS 或 JavaScript——所有动画逻辑都写在 SVG 标签内部。

SMIL 提供了三个核心动画元素:

元素

用途

<animate>

动画化元素的普通属性(如 cx、fill、opacity)

<animateTransform>

动画化元素的变换属性(平移、旋转、缩放)

<animateMotion>

让元素沿路径运动

3.1 <animate> —— 属性动画

想让一个圆在 5 秒内从左向右移动?用 <animate> 直接动画化 cx 属性:

<svg viewBox="0 0 300 100" width="300" height="100">
    <circle cx="0" cy="50" r="15" fill="#3498db">
        <animate attributeName="cx" from="0" to="280" dur="5s" repeatCount="indefinite" />
    </circle>
</svg>

核心属性说明:

  • attributeName —— 要动画的属性名(如 cx、fill、opacity)

  • from —— 起始值

  • to —— 结束值

  • dur —— 持续时间(如 "5s" 表示 5 秒)

  • repeatCount —— 重复次数("indefinite" 表示无限循环)

你也可以在一个元素里添加多个 <animate>,同时动画化多个属性:

<svg viewBox="0 0 300 100" width="300" height="100">
    <rect x="10" y="10" width="100" height="100" fill="#e74c3c">
        <animate attributeName="x" from="10" to="300" dur="3s" repeatCount="indefinite" />
        <animate attributeName="fill" from="#e74c3c" to="#3498db" dur="3s" repeatCount="indefinite" />
    </rect>
</svg>
3.2 <animateTransform> —— 变换动画

<animateTransform> 专门用于动画化 transform 属性,支持平移(translate)、旋转(rotate)、缩放(scale)和斜切(skewX/skewY)。

<svg width="120" height="120" viewBox="0 0 120 120">
  <polygon points="60,30 90,90 30,90" fill="#2ecc71">
    <animateTransform 
      attributeName="transform" 
      type="rotate" 
      from="0 60 70" 
      to="360 60 70" 
      dur="10s" 
      repeatCount="indefinite" 
    />
  </polygon>
</svg>

这里 from="0 60 70" 表示“从 0 度开始旋转,旋转中心在 (60, 70)”。

3.3 <animateMotion> —— 路径动画

这是 SMIL 最酷的功能之一:让元素沿着任意路径运动。

<svg width="400" height="200" viewBox="0 0 400 200">
  <!-- 定义运动路径(可视化显示) -->
  <path id="motionPath" d="M20,150 C80,20 200,180 380,50" fill="none" stroke="#ddd" stroke-width="2" />
  
  <!-- 沿路径运动的圆 -->
  <circle r="12" fill="#e74c3c">
    <animateMotion dur="4s" repeatCount="indefinite">
      <mpath href="#motionPath" />
    </animateMotion>
  </circle>
</svg>

<mpath> 元素引用了一个外部定义的 <path>,让动画元素沿着这个路径运动。绿色圆点为路径上的轨迹点,红色为终点位置。

3.4 SMIL 的现状与注意事项

SMIL 曾经在 Chrome 45 中被标记为“弃用”,但后来 Chrome 开发者中止了弃用计划。目前所有现代浏览器都支持 SMIL。

不过,SMIL 也有明显的局限:

  • 语法相对古老,可读性不如 CSS

  • 调试困难,出错了不容易定位

  • 无法像 JavaScript 那样做复杂的逻辑控制

建议:SMIL 最适合那些纯声明式的、不需要交互逻辑的动画,比如加载动画、装饰性动效、路径运动等。

四、方法三:JavaScript 动画 —— 终极控制权

当 CSS 太“笨”、SMIL 太“死”的时候,就该 JavaScript 出场了。

4.1 原生 JavaScript + requestAnimationFrame

最基础的 JS 动画方式是用 requestAnimationFrame 逐帧更新 SVG 属性:

<svg viewBox="0 0 400 200" width="400" height="200">
  <circle id="ball" cx="50" cy="100" r="20" fill="#3498db" />
</svg>

<script>
  const ball = document.getElementById('ball');
  let x = 50;
  let direction = 1;

  function animate() {
    x += direction * 2;
    if (x > 350 || x < 50) direction *= -1;
    ball.setAttribute('cx', x);
    requestAnimationFrame(animate);
  }

  animate();
</script>

这种方式最灵活——你可以做任何逻辑判断、响应任何用户事件、控制任何属性。但缺点是代码量较大,且需要自己处理缓动函数、时间轴等。

4.2 动画库:GSAP 与 Anime.js

对于复杂的 SVG 动画,直接用原生 JS 写会非常痛苦。这时候动画库就派上用场了。

GSAP(GreenSock Animation Platform)是目前最强大的 Web 动画库之一,对 SVG 的支持尤其出色:

// 让一个圆在 2 秒内沿弧线运动
gsap.to("#ball", {
  duration: 2,
  motionPath: {
    path: "#motionPath",
    align: "#motionPath",
    autoRotate: true
  }
});

GSAP 支持路径动画、变形(morphing)、滚动触发(ScrollTrigger)等高级功能,非常适合专业级的 SVG 动效项目。

Anime.js 是一个更轻量的选择(压缩后约 23KB),语法直观,适合中小型项目:

anime({
  targets: '#ball',
  translateX: 250,
  duration: 2000,
  easing: 'easeInOutQuad',
  loop: true,
  direction: 'alternate'
});
4.3 什么时候该用 JavaScript?

场景

推荐方式

简单的悬停效果、状态切换

CSS transition

循环播放的装饰动画(加载、呼吸)

CSS @keyframes 或 SMIL

沿路径运动

SMIL 或 GSAP

路径变形(morphing)

GSAP 或 Anime.js

复杂的动画序列和时间轴

GSAP

与用户滚动、点击深度绑定的动画

JavaScript + 库

需要精确控制每一帧

原生 requestAnimationFrame

五、三种方法对比:一图看懂怎么选

维度

CSS 动画

SMIL 动画

JavaScript 动画

学习曲线

最平缓

中等

最陡

代码量

最少

较少

最多

灵活性

低

中

最高

调试难度

低

中

中

性能

优秀(GPU 加速)

良好

取决于实现

路径动画

有限支持

原生支持

库支持

交互控制

有限(仅状态切换)

有限

完全控制

适合场景

UI 交互动效

声明式动效

复杂序列、交互

六、性能优化:让你的动画流畅不掉帧

无论用哪种方法,以下几条性能建议都值得牢记:

6.1 优先使用 GPU 加速的属性

transform 和 opacity 是浏览器可以放在 GPU 上处理的属性,动画最流畅。避免动画化 x、y、width、height 等会触发“布局重计算”的属性。

不推荐:

/* 不推荐:动画化 x 属性 → 触发 Layout 重排 */
@keyframes move {
  from { x: 10; }
  to { x: 300; }
}

推荐:

/* 推荐:用 transform 代替 → GPU 直接合成 */
@keyframes move {
  from { transform: translateX(10px); }
  to { transform: translateX(300px); }
}
6.2 保持 SVG 简洁

复杂的 SVG 路径(成千上万个节点)会拖慢动画性能。在设计阶段就考虑优化——使用工具如 SVGO 自动精简代码。

# 安装 SVGO
npm install -g svgo
 
# 压缩 SVG 文件
svgo input.svg -o output.svg
6.3 不要过度动画化

同时运行太多动画会让浏览器不堪重负。如果页面有大量动画元素,考虑:

  • 只对可见区域内的元素运行动画(IntersectionObserver)。

  • 使用 will-change 属性提前告知浏览器哪些元素会变化。

  • 在低端设备上降级或禁用部分动画(prefers-reduced-motion)。

/* 尊重用户的减少动画偏好 */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

七、一个综合示例:让 SVG 图标“活”起来

下面的例子综合运用了三种动画方法,实现一个带有悬停交互动画的图标:

/* CSS 动画:悬停缩放 + 颜色变化 */
.icon-heart svg {
    transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.icon-heart:hover svg {
    transform: scale(1.15);
}
.icon-heart .heart-path {
    transition: fill 0.3s ease;
}
.icon-heart:hover .heart-path {
    fill: #e74c3c;
}

/* CSS 动画:脉冲呼吸效果(自动循环) */
.icon-bell .bell-svg {
    animation: ring 2s ease-in-out infinite;
    transform-origin: 50% 10%;
}
@keyframes ring {
    0%, 100% { transform: rotate(0deg); }
    10% { transform: rotate(15deg); }
    20% { transform: rotate(-12deg); }
    30% { transform: rotate(8deg); }
    40% { transform: rotate(-5deg); }
    50% { transform: rotate(2deg); }
}
.icon-bell:hover .bell-svg {
    animation-duration: 0.8s;
}

/* SMIL 动画:加载旋转(无需 CSS) */
.icon-loading .spinner {
    transform-origin: 50% 50%;
}
.label {
    margin-top: 8px;
    font-size: 12px;
    color: #888;
}

<!-- 示例1:CSS transition —— 悬停缩放 + 变色 -->
<div class="icon-wrapper icon-heart">
    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path class="heart-path" d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
    </svg>
    <div class="label">悬停❤️</div>
</div>

<!-- 示例2:CSS @keyframes —— 呼吸摇铃 -->
<div class="icon-wrapper icon-bell">
    <svg class="bell-svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
        <path d="M13.73 21a2 2 0 0 1-3.46 0" />
    </svg>
    <div class="label">呼吸🔔</div>
</div>

<!-- 示例3:SMIL —— 自动旋转加载 -->
<div class="icon-wrapper icon-loading">
    <svg width="48" height="48" viewBox="0 0 50 50">
        <circle cx="25" cy="25" r="20" fill="none" stroke="#3498db" stroke-width="4" stroke-linecap="round">
            <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="0 25 25"
                    to="360 25 25"
                    dur="1.2s"
                    repeatCount="indefinite"
            />
        </circle>
        <circle cx="25" cy="25" r="20" fill="none" stroke="#FF6600" stroke-width="4" stroke-dasharray="30 95" stroke-linecap="round">
            <animateTransform
                    attributeName="transform"
                    type="rotate"
                    from="360 25 25"
                    to="0 25 25"
                    dur="1.2s"
                    repeatCount="indefinite"
            />
        </circle>
    </svg>
    <div class="label">SMIL 加载</div>
</div>

这个示例展示了三种动画方式的典型应用场景:CSS transition 处理悬停交互、CSS @keyframes 实现循环摇摆、SMIL 驱动自动旋转加载。三者各司其职,组合起来效果非常丰富。

八、总结:你的 SVG 动画工具箱

知识点

一句话总结

适用场景

CSS transition

状态切换类动画的首选,悬停、点击反馈最合适

悬停、点击反馈

CSS @keyframes

循环播放的自动动画,加载、呼吸效果最佳

加载、呼吸、闪烁

SMIL <animate>

SVG 原生属性动画,适合简单数值变化

简单数值变化

SMIL <animateTransform>

SVG 原生变换动画,旋转、缩放、平移

旋转、缩放、平移

SMIL <animateMotion>

SVG 原生路径动画,让元素沿曲线运动

曲线运动

JavaScript + RAF

终极控制权,适合复杂逻辑和交互

复杂逻辑和交互

GSAP / Anime.js

专业级动画库,复杂序列和高级效果

复杂序列和高级效果

性能优化

优先 transform + opacity,保持 SVG 简洁

保持流畅不掉帧

核心选择原则:

  1. 能用 CSS 就不用 SMIL —— CSS 更现代、更容易维护。

  2. 能用 SMIL 就不用原生 JS —— SMIL 代码更少、更声明式。

  3. 复杂场景直接用 GSAP —— 别自己造轮子。

  4. 永远别忘了性能 —— transform 和 opacity 是你的好朋友。

SVG 动画的世界远比这篇文章展示的更加广阔。掌握了这三种核心方法,你就有了探索更复杂效果的坚实基础。下一步,你可以尝试组合使用它们——比如用 CSS 做基础动效,用 SMIL 做路径运动,用 JavaScript 控制播放和交互——让静止的图形真正“活”起来。

下期预告

如果觉得有用,欢迎分享给身边对 SVG 动画感兴趣的朋友。下一期我们将聊聊 SVG 滤镜 —— 如何用代码给图形添加“特效”。