SVG 动画(SMIL / CSS)
SVG 支持多种动画方式:SMIL 动画(<animate>、<animateTransform>、<animateMotion>)、CSS 动画和 JavaScript 控制。SMIL 动画声明在 SVG 内部,无需额外脚本;CSS 动画利用 Web 动画标准;JavaScript 则提供最大灵活性。本节将对比介绍这三种方式的用法和适用场景。
前置知识
阅读本节前,建议先了解:SVG 渐变与滤镜
基础概念
SVG 动画的三种实现方式各有优势:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| SMIL | 声明式、无需 JS | Chrome 曾弃用后恢复、功能有限 | 简单路径动画、循环动画 |
| CSS | 性能好、GPU 加速 | 无法操作 SVG 特有属性 | 变换、透明度、颜色过渡 |
| JavaScript | 完全控制、可交互 | 需要编写代码 | 复杂动画、交互式图形 |
SMIL 动画
animate - 属性动画
<animate> 用于对 SVG 属性进行动画。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SVG SMIL 动画</title>
<style>
svg { border: 1px solid #ccc; display: block; margin: 10px 0; }
</style>
</head>
<body>
<h1>SVG SMIL 动画</h1>
<svg width="500" height="300" viewBox="0 0 500 300">
<!-- 圆形半径动画 -->
<circle cx="100" cy="100" r="20" fill="#4285f4">
<animate attributeName="r" values="20;50;20" dur="2s" repeatCount="indefinite"/>
</circle>
<!-- 矩形颜色动画 -->
<rect x="200" y="50" width="80" height="80" rx="10" fill="#34a853">
<animate attributeName="fill" values="#34a853;#ea4335;#fbbc05;#34a853" dur="4s" repeatCount="indefinite"/>
</rect>
<!-- 透明度动画 -->
<circle cx="380" cy="100" r="40" fill="#ea4335">
<animate attributeName="opacity" values="1;0.3;1" dur="1.5s" repeatCount="indefinite"/>
</circle>
<!-- 位移动画 -->
<rect x="50" y="220" width="40" height="40" fill="#9c27b0">
<animate attributeName="x" values="50;400;50" dur="3s" repeatCount="indefinite"/>
</rect>
</svg>
</body>
</html>animate 常用属性
| 属性 | 说明 | 示例 |
|---|---|---|
attributeName | 要动画的属性 | r、fill、opacity、x |
from | 起始值 | 20 |
to | 结束值 | 50 |
values | 多个关键帧值 | 20;50;20 |
dur | 持续时间 | 2s、500ms |
repeatCount | 重复次数 | indefinite、3 |
begin | 开始时间 | 0s、click |
fill | 动画结束后状态 | freeze、remove |
keyTimes | 关键帧时间点 | 0;0.5;1 |
calcMode | 插值模式 | linear、spline、discrete |
animateTransform - 变换动画
<animateTransform> 用于动画变换操作。
html
<svg width="400" height="300" viewBox="0 0 400 300">
<!-- 旋转动画 -->
<g transform="translate(100, 100)">
<rect x="-30" y="-30" width="60" height="60" fill="#4285f4">
<animateTransform attributeName="transform" type="rotate"
from="0" to="360" dur="3s" repeatCount="indefinite"/>
</rect>
</g>
<!-- 缩放动画 -->
<g transform="translate(250, 100)">
<circle cx="0" cy="0" r="30" fill="#ea4335">
<animateTransform attributeName="transform" type="scale"
values="1;1.5;1" dur="2s" repeatCount="indefinite"/>
</circle>
</g>
<!-- 平移动画 -->
<rect x="50" y="220" width="40" height="40" fill="#34a853">
<animateTransform attributeName="transform" type="translate"
values="0,0;300,0;0,0" dur="4s" repeatCount="indefinite"/>
</rect>
</svg>animateMotion - 沿路径运动
<animateMotion> 使元素沿指定路径运动。
html
<svg width="400" height="300" viewBox="0 0 400 300">
<!-- 定义运动路径 -->
<defs>
<path id="motionPath" d="M 50,200 C 100,50 300,50 350,200 S 200,350 50,200"
fill="none" stroke="#ccc" stroke-dasharray="5,5"/>
</defs>
<!-- 沿路径运动的圆形 -->
<circle r="15" fill="#ea4335">
<animateMotion dur="4s" repeatCount="indefinite" rotate="auto">
<mpath href="#motionPath"/>
</animateMotion>
</circle>
<!-- 沿路径运动的矩形 -->
<rect x="-15" y="-10" width="30" height="20" rx="5" fill="#4285f4">
<animateMotion dur="4s" repeatCount="indefinite" begin="2s" rotate="auto">
<mpath href="#motionPath"/>
</animateMotion>
</rect>
</svg>SMIL 动画控制
html
<!-- 点击开始/暂停动画 -->
<circle cx="100" cy="100" r="30" fill="#4285f4">
<animate attributeName="r" values="30;60;30" dur="2s"
begin="click" repeatCount="indefinite" fill="freeze"/>
</circle>
<!-- 嵌套动画:先等待 1 秒,然后开始 -->
<circle cx="250" cy="100" r="20" fill="#34a853">
<animate attributeName="opacity" values="1;0.2;1" dur="1s"
begin="1s" repeatCount="indefinite"/>
</circle>CSS 动画
CSS transition
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SVG CSS 动画</title>
<style>
svg { border: 1px solid #ccc; display: block; margin: 10px 0; }
/* 鼠标悬停效果 */
.hover-circle {
transition: all 0.3s ease;
cursor: pointer;
}
.hover-circle:hover {
fill: #ea4335;
transform: scale(1.2);
transform-origin: center;
}
/* 脉冲动画 */
.pulse {
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { r: 30; opacity: 1; }
50% { r: 45; opacity: 0.7; }
}
/* 旋转动画 */
.spin {
animation: spin 3s linear infinite;
transform-origin: center;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* 颜色循环 */
.colorCycle circle {
animation: colorChange 4s ease-in-out infinite;
}
@keyframes colorChange {
0% { fill: #4285f4; }
33% { fill: #ea4335; }
66% { fill: #34a853; }
100% { fill: #4285f4; }
}
/* 虚线动画(蚂蚁线) */
.dash-anim {
stroke-dasharray: 8 4;
stroke-dashoffset: 0;
animation: dashMove 1s linear infinite;
}
@keyframes dashMove {
to { stroke-dashoffset: -12; }
}
</style>
</head>
<body>
<h1>SVG CSS 动画</h1>
<svg width="500" height="300" viewBox="0 0 500 300">
<!-- 悬停效果 -->
<circle class="hover-circle" cx="60" cy="60" r="30" fill="#4285f4"/>
<!-- 脉冲动画 -->
<circle class="pulse" cx="180" cy="60" r="30" fill="#34a853"/>
<!-- 旋转动画 -->
<g class="spin" transform="translate(300, 60)">
<rect x="-25" y="-25" width="50" height="50" rx="5" fill="#ea4335"/>
</g>
<!-- 颜色循环 -->
<g class="colorCycle" transform="translate(420, 60)">
<circle cx="0" cy="0" r="30"/>
</g>
<!-- 蚂蚁线动画 -->
<rect class="dash-anim" x="30" y="150" width="200" height="100" rx="8"
fill="none" stroke="#9c27b0" stroke-width="2"/>
<!-- 描边动画 -->
<rect x="280" y="150" width="180" height="100" rx="8"
fill="none" stroke="#4285f4" stroke-width="2"
stroke-dasharray="560" stroke-dashoffset="560"
style="animation: drawLine 2s ease forwards;">
</rect>
<style>
@keyframes drawLine {
to { stroke-dashoffset: 0; }
}
</style>
</svg>
</body>
</html>CSS 动画 vs SMIL 的属性支持
| 属性 | SMIL animate | CSS animation | CSS transition |
|---|---|---|---|
fill / stroke | 支持 | 支持 | 支持 |
opacity | 支持 | 支持 | 支持 |
transform | 支持 | 支持 | 支持 |
r / cx / cy | 支持 | 部分支持 | 不支持 |
d (路径) | 支持 | 不支持 | 不支持 |
points | 支持 | 不支持 | 不支持 |
stroke-dashoffset | 支持 | 支持 | 支持 |
JavaScript 控制
SVG DOM 操作
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>SVG JavaScript 动画</title>
<style>
svg { border: 1px solid #ccc; display: block; margin: 10px 0; }
</style>
</head>
<body>
<h1>SVG JavaScript 控制</h1>
<svg id="jsSvg" width="400" height="300" viewBox="0 0 400 300">
<circle id="jsCircle" cx="200" cy="150" r="40" fill="#4285f4"/>
</svg>
<script>
const circle = document.getElementById('jsCircle');
// 使用 requestAnimationFrame 做动画
let time = 0;
function animate() {
time += 0.02;
// 修改属性
const x = 200 + Math.sin(time) * 100;
const y = 150 + Math.cos(time * 0.7) * 60;
const r = 30 + Math.sin(time * 1.5) * 15;
const hue = (time * 30) % 360;
circle.setAttribute('cx', x);
circle.setAttribute('cy', y);
circle.setAttribute('r', Math.max(5, r));
circle.setAttribute('fill', `hsl(${hue}, 70%, 55%)`);
requestAnimationFrame(animate);
}
animate();
// SVG 元素也支持 CSS 属性操作
// circle.style.fill = 'red';
// circle.style.transform = 'scale(1.5)';
</script>
</body>
</html>使用 Web Animations API
javascript
// SVG 元素也支持 Web Animations API
const circle = document.getElementById('jsCircle');
circle.animate([
{ r: 40, fill: '#4285f4' },
{ r: 60, fill: '#ea4335' },
{ r: 40, fill: '#34a853' },
{ r: 40, fill: '#4285f4' }
], {
duration: 3000,
iterations: Infinity
});交互式 SVG 动画
javascript
// 鼠标跟随效果
const svg = document.getElementById('jsSvg');
const circles = svg.querySelectorAll('.particle');
svg.addEventListener('mousemove', (e) => {
const rect = svg.getBoundingClientRect();
const x = ((e.clientX - rect.left) / rect.width) * 400;
const y = ((e.clientY - rect.top) / rect.height) * 300;
circles.forEach((circle, i) => {
const angle = (i / circles.length) * Math.PI * 2;
const distance = 40 + Math.sin(Date.now() / 500 + i) * 20;
const cx = x + Math.cos(angle) * distance;
const cy = y + Math.sin(angle) * distance;
circle.setAttribute('cx', cx);
circle.setAttribute('cy', cy);
});
});注意事项
1. SMIL 在 Chrome 中的历史
Chrome 曾在 2015 年宣布弃用 SMIL,但后来恢复了支持。SMIL 目前仍然有效,但推荐对于简单动画优先使用 CSS。
2. CSS transform 在 SVG 中的差异
css
/* SVG 元素的 transform-origin 默认是 SVG 画布的原点 (0,0) */
/* 需要显式设置 transform-origin */
.my-element {
transform-origin: center; /* 可能不生效 */
transform-origin: 50% 50%; /* 推荐使用百分比 */
transform-box: fill-box; /* 让 transform-origin 相对于元素边界框 */
}3. 动画性能
css
/* 推荐使用 transform 和 opacity(GPU 加速) */
.fast-animation {
animation: move 1s linear infinite;
/* 触发 GPU 合成层 */
will-change: transform;
}
/* 避免动画化触发重排的属性 */
/* 不好的做法:动画化 width、height、x、y */最佳实践
1. 动画方案选择指南
| 需求 | 推荐方案 |
|---|---|
| 悬停/焦点反馈 | CSS transition |
| 循环装饰动画 | CSS animation |
| 沿路径运动 | SMIL animateMotion |
| 路径变形动画 | SMIL animate(d 属性) |
| 交互式动画 | JavaScript |
| 复杂编排 | JavaScript + Web Animations API |
| 入场动画 | CSS animation + delay |
2. 描边动画模板
css
.draw-path {
stroke-dasharray: var(--path-length);
stroke-dashoffset: var(--path-length);
animation: draw 2s ease forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}javascript
// 动态获取路径长度
const path = document.querySelector('.draw-path');
const length = path.getTotalLength();
path.style.setProperty('--path-length', length);下一节
继续学习:SVG Sprite