svg-animation

v2026.09.24

Animating SVG with CSS, Framer Motion, GSAP and native SVG attributes. Covers SVG fundamentals, path `d` syntax, filters, generative SVG in React and animated gradients. USE WHEN: user mentions "SVG animation", "path morphing", "stroke-dasharray", "SVG filter", "animated icon", asks about animating an inline SVG or drawing a path on scroll DO NOT USE FOR: raster or pixel graphics (use `canvas-webgl`), 3D (use `three-js`), general component animation (use `framer-motion`)

GitHub
安装命令
npx skhub add claude-dev-suite/svg-animation
Markdown
SKILL.md

SVG Animation Skill

Animating SVG with CSS, Framer Motion, GSAP, and native SVG attributes.

SVG Fundamentals

<svg
  xmlns="http://www.w3.org/2000/svg"
  viewBox="0 0 200 200"   <!-- coordinate system, not pixel size -->
  width="200" height="200"
  role="img" aria-label="Description"
>

Coordinate system

  • viewBox="minX minY width height" — defines the internal coordinate space
  • Scales with CSS width/height — use width: 100% for responsive SVG
  • Always set viewBox — never rely on pixel width/height for scaling

Path Syntax (d attribute)

CommandMeaning
M x yMove to (pen up)
L x yLine to
H xHorizontal line
V yVertical line
C cx1 cy1 cx2 cy2 x yCubic bezier
Q cx cy x yQuadratic bezier
A rx ry rot large-arc sweep x yArc
ZClose path

Lowercase = relative coords, uppercase = absolute.

CSS Animation on SVG

Path Draw Effect

/* Step 1: measure path length in JS:
   path.getTotalLength() → e.g. 342 */

.animated-path {
  stroke-dasharray: 342;
  stroke-dashoffset: 342;
  animation: draw 2s ease-in-out forwards;
}

@keyframes draw {
  to { stroke-dashoffset: 0; }
}
// React: measure and animate
function AnimatedPath({ d }: { d: string }) {
  const pathRef = useRef<SVGPathElement>(null);
  const [length, setLength] = useState(0);

  useEffect(() => {
    if (pathRef.current) setLength(pathRef.current.getTotalLength());
  }, []);

  return (
    <path
      ref={pathRef}
      d={d}
      style={{
        strokeDasharray: length,
        strokeDashoffset: length,
        animation: "draw 2s ease-in-out forwards"
      }}
    />
  );
}

Stroke Animation Properties

path {
  stroke: #6366f1;
  stroke-width: 2;
  fill: none;
  stroke-linecap: round;   /* round | butt | square */
  stroke-linejoin: round;
  stroke-dasharray: 10 5;  /* dash gap dash gap */
}

Framer Motion + SVG

Path Drawing with pathLength

// Framer Motion built-in — no manual getTotalLength needed
<motion.path
  d={pathD}
  initial={{ pathLength: 0, opacity: 0 }}
  animate={{ pathLength: 1, opacity: 1 }}
  transition={{ pathLength: { duration: 2, ease: "easeInOut" }, opacity: { duration: 0.3 } }}
/>

Path Morphing

<motion.path
  d={isOpen ? expandedPath : collapsedPath}
  transition={{ duration: 0.4, ease: "easeInOut" }}
/>

Rule: paths must have the same number of commands for morphing to work.

Animated SVG Icon

const iconVariants = {
  closed: { d: "M4 6h16M4 12h16M4 18h16" }, // hamburger
  open:   { d: "M6 18L18 6M6 6l12 12" }       // X
};

function MenuIcon({ isOpen }: { isOpen: boolean }) {
  return (
    <svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor">
      <motion.path
        strokeLinecap="round"
        strokeLinejoin="round"
        strokeWidth={2}
        variants={iconVariants}
        animate={isOpen ? "open" : "closed"}
        transition={{ duration: 0.3 }}
      />
    </svg>
  );
}

GSAP + SVG

// Stagger SVG element entrance
gsap.from("svg path", {
  opacity: 0,
  scale: 0,
  transformOrigin: "center",
  stagger: 0.05,
  duration: 0.4,
  ease: "back.out(1.7)"
});

// Draw path manually
function drawPath(el: SVGPathElement) {
  const len = el.getTotalLength();
  gsap.set(el, { strokeDasharray: len, strokeDashoffset: len });
  gsap.to(el, { strokeDashoffset: 0, duration: 2, ease: "power2.inOut" });
}

SVG Filters

<defs>
  <!-- Glow -->
  <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
    <feGaussianBlur stdDeviation="4" result="blur" />
    <feMerge>
      <feMergeNode in="blur" />
      <feMergeNode in="SourceGraphic" />
    </feMerge>
  </filter>

  <!-- Grain/noise texture -->
  <filter id="grain">
    <feTurbulence type="fractalNoise" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch" />
    <feColorMatrix type="saturate" values="0" />
    <feBlend in="SourceGraphic" in2="noise" mode="overlay" result="blend" />
    <feComposite in="blend" in2="SourceGraphic" operator="in" />
  </filter>

  <!-- Displacement -->
  <filter id="wave">
    <feTurbulence type="turbulence" baseFrequency="0.02" numOctaves="3" result="turb">
      <animate attributeName="baseFrequency" values="0.02;0.04;0.02" dur="4s" repeatCount="indefinite" />
    </feTurbulence>
    <feDisplacementMap in="SourceGraphic" in2="turb" scale="20" />
  </filter>
</defs>

Apply: <use filter="url(#glow)" /> or inline on element.

Generative SVG (React)

// Generative blob
function Blob({ seed = 0 }: { seed?: number }) {
  const points = 8;
  const coords = Array.from({ length: points }, (_, i) => {
    const angle = (i / points) * Math.PI * 2;
    const r = 80 + Math.sin(seed + i * 1.3) * 20;
    return [100 + Math.cos(angle) * r, 100 + Math.sin(angle) * r];
  });

  // Build smooth closed path
  const d = coords.map((p, i) => {
    const next = coords[(i + 1) % points];
    const mx = (p[0] + next[0]) / 2;
    const my = (p[1] + next[1]) / 2;
    return `${i === 0 ? "M" : "Q"} ${p[0]} ${p[1]} ${mx} ${my}`;
  }).join(" ") + " Z";

  return <path d={d} fill="#6366f1" />;
}

Animated SVG Gradient

function AnimatedGradientCircle() {
  return (
    <svg viewBox="0 0 200 200">
      <defs>
        <radialGradient id="grad" cx="50%" cy="50%" r="50%">
          <stop offset="0%" stopColor="#6366f1">
            <animate attributeName="stop-color"
              values="#6366f1;#a855f7;#ec4899;#6366f1"
              dur="4s" repeatCount="indefinite" />
          </stop>
          <stop offset="100%" stopColor="transparent" />
        </radialGradient>
      </defs>
      <circle cx="100" cy="100" r="80" fill="url(#grad)" />
    </svg>
  );
}

Accessibility

<!-- Informative SVG -->
<svg role="img" aria-labelledby="title desc">
  <title id="title">Chart title</title>
  <desc id="desc">Detailed description for screen readers</desc>
  ...
</svg>

<!-- Decorative SVG -->
<svg aria-hidden="true" focusable="false">
  ...
</svg>

Pitfalls

  • transform-origin in SVG ≠ CSS — use transform-box: fill-box to make it behave like CSS
  • SVG path morphing requires same point count — simplify paths with tools like SVGOMG
  • stroke-dasharray values must match getTotalLength() precisely — measure after render
  • Filters with percentage units expand beyond SVG bounds — use primitiveUnits="objectBoundingBox" or explicit overflow
  • viewBox without preserveAspectRatio="xMidYMid meet" (default) may distort on resize
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/graphics/svg-animation

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1