svg-animation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SVG Animation

SVG动画

Crisp, lightweight, infinitely scalable vector motion — ideal for icons, illustrations, logos, and data marks. SVG can be animated three ways: CSS (declarative, simple), SMIL (
<animate>
inside the SVG), and JS (GSAP/Web Animations, for control and morphing). Choose per task; the techniques below say which.
清晰、轻量、可无限缩放的矢量动效——非常适合图标、插画、Logo和数据标记。SVG有三种动画实现方式:CSS(声明式,简单)、SMIL(在SVG内部使用
<animate>
标签)和JS(GSAP/Web Animations,用于精准控制和变形)。可根据任务选择合适的方式,以下技术会说明适用场景。

When to use

适用场景

  • Stroke "draw-on" of icons, illustrations, signatures, maps
  • Shape/path morphing and animated icon state changes (menu ↔ close, play ↔ pause)
  • Moving an element along a path (motion path)
  • Animated gradients, filters (glow, displacement), and animated logos
  • 图标、插画、签名、地图的描边「绘制」效果
  • 形状/路径变形以及图标状态切换动画(菜单↔关闭,播放↔暂停)
  • 元素沿路径移动(运动路径)
  • 带动画的渐变、滤镜(发光、位移)以及动画Logo

Core techniques

核心技术

Stroke draw-on (the staple)

描边绘制(核心技巧)

Draw the dash array as long as the path, offset it fully (invisible), then animate the offset to 0.
css
.path {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: draw 1.4s ease forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
Getting the length:
  • JS (most reliable):
    const len = path.getTotalLength(); path.style.setProperty("--len", len);
  • No-JS trick: set
    pathLength="1"
    on the
    <path>
    , then
    stroke-dasharray: 1; stroke-dashoffset: 1;
    and animate to
    0
    . This normalizes any path to a 0–1 length so no measurement is needed.
Reverse (erase) by animating offset from 0 back to
len
. Stagger multiple paths with
animation-delay
. Direction of drawing follows the path's point order; reverse it in the editor or negate the offset sign if it draws "backwards".
将虚线数组设置为路径的长度,完全偏移(不可见),然后将偏移量动画到0。
css
.path {
  stroke-dasharray: var(--len);
  stroke-dashoffset: var(--len);
  animation: draw 1.4s ease forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
获取路径长度的方法:
  • JS(最可靠):
    const len = path.getTotalLength(); path.style.setProperty("--len", len);
  • 无JS技巧:在
    <path>
    上设置
    pathLength="1"
    ,然后设置
    stroke-dasharray: 1; stroke-dashoffset: 1;
    并动画到
    0
    。这会将任何路径归一化为0–1的长度,无需测量。
通过将偏移量从0动画回
len
实现反向(擦除)效果。使用
animation-delay
实现多条路径的交错动画。绘制方向遵循路径的点顺序;如果绘制方向相反,可在编辑器中反转路径,或反转偏移量的符号。

Morphing one path into another

路径变形

Paths interpolate point-by-point, so a naive morph requires both
d
attributes to have the same number and type of commands. Two robust approaches:
  • GSAP MorphSVG (free as of GSAP 3.12) — handles mismatched point counts automatically and finds a good mapping:
js
gsap.registerPlugin(MorphSVGPlugin);
gsap.to("#start", { morphSVG: "#end", duration: 0.8, ease: "power2.inOut" });
// Convert any shape to a morph-able path:
MorphSVGPlugin.convertToPath("circle, rect, ellipse, line, polygon");
  • Flubber (small standalone lib) — generates interpolators without GSAP, good with React/Framer Motion:
js
import { interpolate } from "flubber";
const interpolator = interpolate(pathA, pathB, { maxSegmentLength: 2 });
// interpolator(0) === pathA, interpolator(1) === pathB; feed t into a tween.
For hand-authored morphs (icon toggles), keep both paths with identical command structure and animate
d
directly via Web Animations or CSS (
d
is animatable in modern browsers via
path("...")
).
路径是逐点插值的,因此简单的变形要求两个路径的
d
属性具有相同数量和类型的命令。以下两种可靠方法:
  • GSAP MorphSVG(GSAP 3.12起免费)——自动处理不匹配的点数并找到合适的映射:
js
gsap.registerPlugin(MorphSVGPlugin);
gsap.to("#start", { morphSVG: "#end", duration: 0.8, ease: "power2.inOut" });
// 将任何形状转换为可变形的路径:
MorphSVGPlugin.convertToPath("circle, rect, ellipse, line, polygon");
  • Flubber(小型独立库)——无需GSAP即可生成插值器,适用于React/Framer Motion:
js
import { interpolate } from "flubber";
const interpolator = interpolate(pathA, pathB, { maxSegmentLength: 2 });
// interpolator(0) === pathA, interpolator(1) === pathB; 将t值传入补间动画。
对于手动编写的变形效果(图标切换),保持两个路径的命令结构完全相同,直接通过Web Animations或CSS动画
d
属性(现代浏览器中
d
可通过
path("...")
实现动画)。

Motion along a path

沿路径运动

  • GSAP MotionPath (preferred — control, alignment, scrub):
js
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#rocket", {
  duration: 4, repeat: -1, ease: "none",
  motionPath: { path: "#track", align: "#track", autoRotate: true, alignOrigin: [0.5, 0.5] },
});
autoRotate: true
orients the object to the path tangent;
align
makes coordinates relative to the path element.
  • SMIL (no JS):
svg
<path id="track" d="M10,80 C40,10 120,10 150,80" fill="none"/>
<circle r="6" fill="#3b82f6">
  <animateMotion dur="3s" repeatCount="indefinite" rotate="auto">
    <mpath href="#track"/>
  </animateMotion>
</circle>
  • CSS offset-path (modern, declarative):
    offset-path: path("M10,80 C..."); animation: move 3s linear infinite;
    with
    @keyframes move { to { offset-distance: 100%; } }
    and
    offset-rotate: auto
    .
  • GSAP MotionPath(首选——可控、对齐、可 scrub):
js
gsap.registerPlugin(MotionPathPlugin);
gsap.to("#rocket", {
  duration: 4, repeat: -1, ease: "none",
  motionPath: { path: "#track", align: "#track", autoRotate: true, alignOrigin: [0.5, 0.5] },
});
autoRotate: true
会使对象与路径切线方向对齐;
align
使坐标相对于路径元素。
  • SMIL(无需JS):
svg
<path id="track" d="M10,80 C40,10 120,10 150,80" fill="none"/>
<circle r="6" fill="#3b82f6">
  <animateMotion dur="3s" repeatCount="indefinite" rotate="auto">
    <mpath href="#track"/>
  </animateMotion>
</circle>
  • CSS offset-path(现代、声明式):
    offset-path: path("M10,80 C..."); animation: move 3s linear infinite;
    配合
    @keyframes move { to { offset-distance: 100%; } }
    offset-rotate: auto

Animated gradients and filters

动画渐变和滤镜

Gradients: animate
gradientTransform
or stop offsets. A sheen sweep:
svg
<linearGradient id="sheen">
  <stop offset="0%"  stop-color="#fff" stop-opacity="0"/>
  <stop offset="50%" stop-color="#fff" stop-opacity=".8"/>
  <stop offset="100%" stop-color="#fff" stop-opacity="0"/>
  <animateTransform attributeName="gradientTransform" type="translate"
    from="-1 0" to="1 0" dur="2s" repeatCount="indefinite"/>
</linearGradient>
Filters: animate
feDisplacementMap
scale
for gooey/wobble,
feGaussianBlur
stdDeviation
for focus pulls, or
feColorMatrix
/
feFlood
for glow. Filters are paint-heavy — animate sparingly and prefer
transform
/
opacity
where possible.
渐变:动画
gradientTransform
或停止偏移量。示例为光泽扫过效果:
svg
<linearGradient id="sheen">
  <stop offset="0%"  stop-color="#fff" stop-opacity="0"/>
  <stop offset="50%" stop-color="#fff" stop-opacity=".8"/>
  <stop offset="100%" stop-color="#fff" stop-opacity="0"/>
  <animateTransform attributeName="gradientTransform" type="translate"
    from="-1 0" to="1 0" dur="2s" repeatCount="indefinite"/>
</linearGradient>
滤镜:动画
feDisplacementMap
scale
实现粘性/抖动效果,动画
feGaussianBlur
stdDeviation
实现焦点切换,或动画
feColorMatrix
/
feFlood
实现发光效果。滤镜绘制成本高——应尽量少用动画,优先使用
transform
/
opacity

Implementation choice (pick fast)

实现方式选择(选最快的)

NeedUse
Single declarative draw/fadeCSS
Self-contained, no JS bundleSMIL (
<animate*>
in the SVG)
Coordinated, scrubbable, scroll-tiedGSAP
Mismatched-point morphGSAP MorphSVG or Flubber
Path following with rotationGSAP MotionPath / CSS offset-path
SMIL caveat: not supported in IE/old Edge and historically deprecation-flagged; for max reach or scroll-syncing, prefer CSS or JS. SMIL is still fine for self-contained icon assets in evergreen browsers.
需求使用方式
单一声明式绘制/淡入CSS
自包含,无需JS包SMIL(SVG内部的
<animate*>
标签)
协同动画、可 scrub、与滚动绑定GSAP
点数不匹配的路径变形GSAP MorphSVG 或 Flubber
带旋转的路径跟随GSAP MotionPath / CSS offset-path
SMIL注意事项:不支持IE/旧版Edge,且曾被标记为废弃;如需最大兼容性或滚动同步,优先选择CSS或JS。在现代浏览器中,SMIL仍适用于自包含的图标资源。

Authoring and optimization

创作与优化

  • Build/clean with SVGO: keep
    viewBox
    , drop editor metadata, but disable
    cleanupIds
    /
    removeViewBox
    and any plugin that renames IDs you reference from CSS/JS/SMIL. Disable
    mergePaths
    and
    convertShapeToPath
    if you animate individual sub-paths or shapes.
  • Inline the SVG in the DOM (not
    <img src>
    ) so CSS/JS can reach its internals;
    <img>
    -embedded SVG can only self-animate via internal SMIL/CSS.
  • Set explicit
    viewBox
    and avoid fixed
    width
    /
    height
    so the asset scales fluidly.
  • For draw-on, ensure paths are actual strokes (
    fill:none; stroke:...
    ), not filled outlines — dashoffset only affects strokes.
  • Respect
    prefers-reduced-motion
    : gate looping/large motion; keep a static final state.
  • 使用SVGO构建/清理SVG:保留
    viewBox
    ,删除编辑器元数据,但禁用
    cleanupIds
    /
    removeViewBox
    以及任何会重命名你在CSS/JS/SMIL中引用的ID的插件。如果你要为单个子路径或形状添加动画,请禁用
    mergePaths
    convertShapeToPath
  • 将SVG内联到DOM中(而非
    <img src>
    ),以便CSS/JS可以访问其内部元素;
    <img>
    嵌入的SVG只能通过内部SMIL/CSS实现自动画。
  • 设置明确的
    viewBox
    ,避免固定的
    width
    /
    height
    ,以便资源可以流畅缩放。
  • 实现绘制效果时,确保路径是实际的描边(
    fill:none; stroke:...
    ),而非填充轮廓——dashoffset仅影响描边。
  • 尊重
    prefers-reduced-motion
    :限制循环/大动效;保留静态最终状态。

Deliver & verify (standalone HTML)

交付与验证(独立HTML)

Packaged helper (
scripts/
):
scripts/seek-shot.sh anim.html 0 1.5 3
freezes the
?t=N
harness and screenshots each moment;
scripts/contact-sheet.sh sheet.png frame-*.png
tiles them for one-glance review. See
scripts/README.md
.
For a self-contained icon/logo/draw-on the deliverable is one HTML file that opens directly in a browser — inline the SVG in the markup, drive the animation with one mechanism, no build step. One file is the right tier for a vector asset; don't reach for a bundler.
Output contract:
  • One
    .html
    file: inline
    <svg>
    , plus CSS
    @keyframes
    / a
    <script>
    with GSAP from CDN / SMIL
    <animate*>
    — pick one driver.
  • Include the seek harness matching that driver so any moment can be frozen for a screenshot.
Seek harness — freeze an exact moment.
?t=N
seeks and pauses so a screenshot lands on a still frame. Use the mechanism that matches how the SVG animates:
html
<script>
  const t = new URLSearchParams(location.search).get("t");
  if (t !== null) {
    const N = parseFloat(t);
    // SMIL: pause the SVG's own clock and scrub it
    const svg = document.querySelector("svg");
    svg.pauseAnimations(); svg.setCurrentTime(N);
    // CSS @keyframes draw-on: el.style.animationDelay=(-N)+"s"; el.style.animationPlayState="paused";
    // GSAP timeline: tl.pause(); tl.seek(N);
  }
  window.__ready = true;
</script>
Verify loop — render → freeze → screenshot → check: open the file at start / mid / end (
?t=0
,
?t=<dur/2>
,
?t=<dur>
), screenshot each, and check fidelity (stroke draws in the right direction, morph endpoints clean) plus artifacts (path clipped by
viewBox
, stroke vanishing from a stale dashoffset, FOUC, jank at the morph seam). Any headless tool works:
bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/icon.html?t=0.7" frame-mid.png
Before you finish:
  1. Opens standalone — no console errors, inline SVG reachable, CDN (if any) loads.
  2. The seek mechanism for your driver freezes a deterministic frame.
  3. Screenshotted at start / mid / end — matches the brief, no clipping or off-
    viewBox
    strokes.
  4. prefers-reduced-motion
    honored — looping/large motion gated, static final state kept.
  5. Easing is intentional —
    ease
    /GSAP ease chosen on purpose, no accidental
    linear
    draw-on.
打包工具
scripts/
目录):
scripts/seek-shot.sh anim.html 0 1.5 3
会冻结
?t=N
机制并截取每个时刻的截图;
scripts/contact-sheet.sh sheet.png frame-*.png
将截图拼接成一张预览图。详见
scripts/README.md
对于自包含的图标/Logo/绘制效果,交付物应为可直接在浏览器中打开的单个HTML文件——将SVG内联到标记中,使用一种机制驱动动画,无需构建步骤。单个文件是矢量资源的合适交付形式;无需使用打包工具。
输出规范:
  • 一个
    .html
    文件:内联
    <svg>
    ,加上CSS
    @keyframes
    / 引用CDN的GSAP
    <script>
    / SMIL
    <animate*>
    标签——选择一种驱动方式。
  • 包含与驱动方式匹配的seek机制,以便可以冻结任何时刻进行截图。
Seek机制——冻结精确时刻
?t=N
会跳转到指定时刻并暂停,以便截图获取静态帧。使用与SVG动画方式匹配的机制:
html
<script>
  const t = new URLSearchParams(location.search).get("t");
  if (t !== null) {
    const N = parseFloat(t);
    // SMIL:暂停SVG自身的时钟并调整到指定时刻
    const svg = document.querySelector("svg");
    svg.pauseAnimations(); svg.setCurrentTime(N);
    // CSS @keyframes绘制效果:el.style.animationDelay=(-N)+"s"; el.style.animationPlayState="paused";
    // GSAP时间线:tl.pause(); tl.seek(N);
  }
  window.__ready = true;
</script>
验证循环——渲染→冻结→截图→检查:在开始/中间/结束时刻打开文件(
?t=0
?t=<dur/2>
?t=<dur>
),分别截图,检查保真度(描边绘制方向正确,变形端点清晰)以及瑕疵(路径被
viewBox
裁剪,描边因过期的dashoffset消失,FOUC,变形接缝处卡顿)。任何无头工具都可使用:
bash
npx playwright screenshot --wait-for-timeout=500 "file://$PWD/icon.html?t=0.7" frame-mid.png
完成前检查:
  1. 可独立打开——无控制台错误,内联SVG可访问,CDN(如有)加载正常。
  2. 对应驱动方式的seek机制可冻结确定的帧。
  3. 在开始/中间/结束时刻截图——符合需求,无裁剪或超出
    viewBox
    的描边。
  4. 遵循
    prefers-reduced-motion
    ——限制循环/大动效,保留静态最终状态。
  5. 缓动效果符合预期——
    ease
    /GSAP缓动是有意选择的,而非意外使用
    linear
    绘制效果。

Reference files

参考文件

  • references/svg-techniques.md
    — full dashoffset math and
    getTotalLength
    gotchas, the
    pathLength="1"
    normalization, GSAP MorphSVG vs Flubber decision guide with code, an icon-toggle morph (hamburger↔close), MotionPath/offset-path details, SMIL-vs-CSS-vs-JS tradeoffs, and an SVGO config tuned for animation.
  • references/svg-techniques.md
    ——完整的dashoffset数学计算和
    getTotalLength
    注意事项,
    pathLength="1"
    归一化方法,GSAP MorphSVG与Flubber的选择指南及代码示例,图标切换变形(汉堡↔关闭),MotionPath/offset-path细节,SMIL vs CSS vs JS的权衡,以及针对动画优化的SVGO配置。