你有没有见过这样的页面:一个加载图标在不停旋转,一个按钮在悬停时优雅地变色,一个图表的数据在页面加载时缓缓“绘制”出来?这些让人眼前一亮的动态效果,背后很大一部分功臣就是 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.svg6.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 简洁 | 保持流畅不掉帧 |
核心选择原则:
能用 CSS 就不用 SMIL —— CSS 更现代、更容易维护。
能用 SMIL 就不用原生 JS —— SMIL 代码更少、更声明式。
复杂场景直接用 GSAP —— 别自己造轮子。
永远别忘了性能 —— transform 和 opacity 是你的好朋友。
SVG 动画的世界远比这篇文章展示的更加广阔。掌握了这三种核心方法,你就有了探索更复杂效果的坚实基础。下一步,你可以尝试组合使用它们——比如用 CSS 做基础动效,用 SMIL 做路径运动,用 JavaScript 控制播放和交互——让静止的图形真正“活”起来。
下期预告
如果觉得有用,欢迎分享给身边对 SVG 动画感兴趣的朋友。下一期我们将聊聊 SVG 滤镜 —— 如何用代码给图形添加“特效”。