# SVG Animation Techniques (detailed) ## 1. Stroke draw-on: the math `stroke-dasharray` defines a dash pattern; `stroke-dashoffset` shifts where the pattern starts. Set the dash equal to the whole path length and offset it by that same length → the visible dash is pushed entirely off the path (invisible). Animating offset to 0 slides the dash into view, revealing the stroke progressively. ``` visible fraction = 1 - (dashoffset / pathLength) ``` So offset = length → 0% drawn; offset = 0 → 100% drawn; offset = length/2 → 50% drawn (useful for scroll mapping: `offset = length * (1 - progress)`). ### getTotalLength gotchas ```js const path = document.querySelector("#sig"); const len = path.getTotalLength(); // returns user units of the path geometry path.style.strokeDasharray = len; path.style.strokeDashoffset = len; requestAnimationFrame(() => { // next frame so the transition has a start value path.style.transition = "stroke-dashoffset 1.6s ease"; path.style.strokeDashoffset = "0"; }); ``` - `getTotalLength()` measures the path's own coordinate system, ignoring CSS transforms/scaling — which is what you want for dasharray. - It only works on geometry elements (`path, line, polyline, polygon, circle, ellipse, rect`). For `` draw-on you must convert text to paths first. - Rounding: browsers can clip the last sub-pixel; pad slightly (`len + 1`) if the end never fully closes. - Compound paths (multiple `M` subpaths in one `d`) draw all subpaths simultaneously. Split into separate ``s with staggered `animation-delay` for sequential drawing. ### No-JS normalization with pathLength `pathLength="1"` tells the browser to treat the path's length as exactly 1, regardless of real geometry. Then dash math is unit-free: ```svg ``` ```css @keyframes draw { to { stroke-dashoffset: 0; } } ``` This is the cleanest pure-CSS draw-on and avoids hardcoding measured lengths that break when the path is edited. Use `pathLength="100"` if you prefer to think in percentages. ### Scroll-driven draw-on (no library, via offset) ```js const len = path.getTotalLength(); path.style.strokeDasharray = len; function onScroll() { const p = Math.min(1, Math.max(0, scrollProgress())); // 0..1 path.style.strokeDashoffset = len * (1 - p); } document.addEventListener("scroll", onScroll, { passive: true }); ``` ## 2. Morphing ### Why naive morphs break Path interpolation walks the `d` command list of A and B in lockstep. If A has 4 cubic curves and B has 7, or A uses `L` where B uses `C`, the browser/animator either refuses or produces garbage. Fixes: match commands by hand, or use a library that resamples. ### GSAP MorphSVG (best for arbitrary shapes) ```js import gsap from "gsap"; import { MorphSVGPlugin } from "gsap/MorphSVGPlugin"; gsap.registerPlugin(MorphSVGPlugin); // any shape -> path so it can morph MorphSVGPlugin.convertToPath("#circ, #star"); gsap.to("#circ", { duration: 0.9, ease: "power2.inOut", morphSVG: { shape: "#star", shapeIndex: "auto", type: "rotational" }, }); ``` - `shapeIndex` rotates the starting point mapping to minimize twisting; `"auto"` lets GSAP pick, or pass a number to tune. - `type: "rotational"` interpolates angles/lengths instead of raw coordinates — usually smoother for organic morphs. - `map: "size" | "position" | "complexity"` changes how sub-paths in compound shapes are matched. ### Flubber (standalone, framework-friendly) ```js import { interpolate, separate, combine } from "flubber"; // 1 -> 1 shape const lerp = interpolate(pathA, pathB, { maxSegmentLength: 2 }); // splitting one shape into many (or vice versa) const split = separate(oneShape, [shape1, shape2, shape3], { single: true }); ``` Drive `lerp(t)` with any tween/`requestAnimationFrame`, or inside Framer Motion: ```jsx import { motion, useMotionValue, useTransform, animate } from "motion/react"; const progress = useMotionValue(0); const d = useTransform(progress, [0, 1], [0, 1], { clamp: false }); const path = useTransform(d, (v) => interpolate(A, B)(v)); // animate(progress, 1, { duration: 0.6 }) then ``` Use Flubber when you can't add GSAP or need React-native value plumbing; use MorphSVG for the highest-quality mapping and convenience. ### Hand-authored icon toggle (hamburger ↔ close) — no morph library When you control both shapes, give them identical structure and animate transforms instead of `d`: ```svg ``` ```css .menu line { stroke:#111; stroke-width:2; transition: transform .25s ease, opacity .2s ease; transform-origin: center; transform-box: fill-box; } .menu.open .top { transform: translateY(6px) rotate(45deg); } .menu.open .mid { opacity: 0; } .menu.open .bot { transform: translateY(-6px) rotate(-45deg); } ``` `transform-box: fill-box` makes `transform-origin: center` resolve to the element's own bounding box — essential for SVG rotation; without it the origin is the SVG viewport origin. ## 3. Motion along a path — details ### GSAP MotionPath ```js gsap.to("#dot", { duration: 5, repeat: -1, ease: "none", motionPath: { path: "#route", align: "#route", // coordinates relative to the path element alignOrigin: [0.5, 0.5], // pin the object's center to the path autoRotate: true, // or a number to add a constant offset angle start: 0, end: 1, // animate a sub-segment, e.g. 0.2..0.8 }, }); ``` Convert a series of points into a smooth path: `MotionPathPlugin.convertToPath(...)`, or pass `motionPath: [{x:0,y:0},{x:100,y:50},...]` and GSAP draws a curve through them. ### CSS offset-path (declarative, modern) ```css .dot { offset-path: path("M10,80 C40,10 120,10 150,80"); offset-rotate: auto; /* face direction of travel */ animation: travel 3s linear infinite; } @keyframes travel { to { offset-distance: 100%; } } ``` Newer syntax also accepts `offset-path: url(#route)` referencing a `` id and shapes like `ray()`. Good support in evergreen browsers; no JS needed. ### SMIL animateMotion Self-contained inside the SVG; use `rotate="auto"` and ``. Best for portable icon assets where you don't want external CSS/JS. Avoid when you need scroll-sync or broad legacy support. ## 4. SMIL vs CSS vs JS — tradeoffs | Axis | SMIL | CSS | JS (GSAP/WAAPI) | |------|------|-----|-----------------| | Bundle cost | none (inline) | none | library (GSAP ~JS) | | Works in `` SVG | yes | yes (internal) | no | | Reach (legacy IE/Edge) | no | partial | yes | | Scroll/scrub sync | hard | hard | easy | | Morph mismatched paths | no | no | yes (MorphSVG/Flubber) | | Timeline orchestration | limited | limited | full | | Animate `d`/gradients/filters | yes | partial | yes | Default: CSS for simple declarative effects, GSAP for anything coordinated/scrubbable/morphing, SMIL only for portable self-animating assets in evergreen targets. ## 5. SVGO config tuned for animation Aggressive SVGO defaults will strip the very things you animate. Use a config like: ```js // svgo.config.js module.exports = { multipass: true, plugins: [ { name: "preset-default", params: { overrides: { removeViewBox: false, // keep responsive scaling cleanupIds: false, // DO NOT rename ids referenced by CSS/JS/SMIL/gradients mergePaths: false, // keep sub-paths separate for staggered draw-on convertShapeToPath: false, // keep / if SMIL/CSS targets them removeHiddenElems: false, // keep elements you toggle visible via animation inlineStyles: false, // preserve class hooks } } }, { name: "removeDimensions" }, // drop width/height, keep viewBox -> fluid scaling { name: "prefixIds" }, // namespace ids if inlining many SVGs to avoid id collisions ], }; ``` If multiple inlined SVGs share gradient/filter ids, id collisions make the LAST definition win for everyone — `prefixIds` (or unique authoring) prevents this classic bug. ## 6. Reduced motion ```css @media (prefers-reduced-motion: reduce) { .path { animation: none; stroke-dashoffset: 0; } /* show final drawn state instantly */ } ``` For looping decorative SVG (spinners, ambient gradients), pause or hide them under reduced-motion; for functional icon toggles, keep the state change but drop the easing duration. --- Pick the right method per shape and SVG draws, morphs, and travels smoothly. Built by **[iart.ai](https://iart.ai/?utm_source=github&utm_medium=readme&utm_campaign=web-animation-skills&utm_content=skill_footer&utm_term=svg-animation)** — the AI motion agent for editable, on-brand motion graphics.