SVG Animation
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.
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
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.
Getting the length:
- JS (most reliable):
const len = path.getTotalLength(); path.style.setProperty("--len", len); - No-JS trick: set
pathLength="1"on the<path>, thenstroke-dasharray: 1; stroke-dashoffset: 1;and animate to0. 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".
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:
- Flubber (small standalone lib) — generates interpolators without GSAP, good with React/Framer Motion:
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("...")).
Motion along a path
- GSAP MotionPath (preferred — control, alignment, scrub):
autoRotate: true orients the object to the path tangent; align makes coordinates relative to the path element.
- SMIL (no JS):
- CSS offset-path (modern, declarative):
offset-path: path("M10,80 C..."); animation: move 3s linear infinite;with@keyframes move { to { offset-distance: 100%; } }andoffset-rotate: auto.
Animated gradients and filters
Gradients: animate gradientTransform or stop offsets. A sheen sweep:
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.
Implementation choice (pick fast)
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.
Authoring and optimization
- Build/clean with SVGO: keep
viewBox, drop editor metadata, but disablecleanupIds/removeViewBoxand any plugin that renames IDs you reference from CSS/JS/SMIL. DisablemergePathsandconvertShapeToPathif 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
viewBoxand avoid fixedwidth/heightso 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.
Deliver & verify (standalone HTML)
Packaged helper (
scripts/):scripts/seek-shot.sh anim.html 0 1.5 3freezes the?t=Nharness and screenshots each moment;scripts/contact-sheet.sh sheet.png frame-*.pngtiles them for one-glance review. Seescripts/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
.htmlfile: 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:
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:
Before you finish:
- Opens standalone — no console errors, inline SVG reachable, CDN (if any) loads.
- The seek mechanism for your driver freezes a deterministic frame.
- Screenshotted at start / mid / end — matches the brief, no clipping or off-
viewBoxstrokes. prefers-reduced-motionhonored — looping/large motion gated, static final state kept.- Easing is intentional —
ease/GSAP ease chosen on purpose, no accidentallineardraw-on.
Reference files
references/svg-techniques.md— full dashoffset math andgetTotalLengthgotchas, thepathLength="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.

