{"course":{"slug":"motion-and-audio","title":"Motion, Sound and Music","url":"https://addaly.com/learn/motion-and-audio","level":"intermediate","track":"create"},"lesson":{"slug":"svg-animation","title":"Animating a drawing that is also a document","url":"https://addaly.com/learn/motion-and-audio/svg-animation","position":21,"of":72,"minutes":10,"answer":"SVG elements sit in the DOM, so CSS and JavaScript animate them directly, and the two techniques worth memorising are stroke-dashoffset for a self-drawing line, normalised with pathLength.","keyIdea":"SVG elements sit in the DOM, so CSS and JavaScript animate them directly, and the two techniques worth memorising are stroke-dashoffset for a self-drawing line, normalised with pathLength, and transform-box: fill-box to stop a rotation swinging across the viewBox."},"body":{"format":"markdown","text":"## SVG is a document, which is the whole trick\n\nAn SVG is markup. Every shape in it is an element with attributes, sitting in the same DOM as the rest of your page. That means the entire CSS and JavaScript animation toolkit applies to it directly: no runtime, no plugin, no export step. A designer draws a path in Inkscape, you give it a class, and CSS animates it.\n\nThis is why SVG remains the right answer for icons, logos, diagrams, line illustrations and data graphics, despite Lottie existing.\n\n## The line that draws itself\n\nThe single most useful SVG technique, and it looks like magic until you know the mechanism.\n\nA stroked path can have a dash pattern: `stroke-dasharray: 10 5` means 10 units of ink, 5 of gap, repeating. Set the dash length equal to the *entire length of the path* and the gap equal to it too, and you have one dash covering the whole line and one gap after it. Then `stroke-dashoffset` slides that pattern along the path. Offset it by the full length and the ink is pushed off the end: the line is invisible. Animate the offset back to zero and the ink slides into place, which reads as the line being drawn.\n\n```css\n.signature path {\n  stroke-dasharray: 1000;\n  stroke-dashoffset: 1000;\n  animation: draw 2s ease-out forwards;\n}\n@keyframes draw { to { stroke-dashoffset: 0; } }\n```\n\nThe number 1000 has to be at least the path's length. Get the real one with `path.getTotalLength()` in JavaScript, or, much better, put `pathLength=\"100\"` on the path itself. That attribute tells SVG to pretend the path is 100 units long for all dash arithmetic, whatever its real geometry, so your CSS can use 100 everywhere and never needs measuring. Almost nobody knows about `pathLength` and it removes the one awkward step in the technique.\n\n## Morphing one shape into another\n\nTwo routes, with a sharp limitation on both.\n\n**Animating the `d` attribute.** Modern browsers can interpolate a path's `d` between two values, in CSS or the Web Animations API. It only works when both paths have the same number of segments, of the same types, in the same order. A four-point square morphs to a four-point diamond; a square does not morph to a circle unless you build the circle out of the same four cubic segments.\n\n**A shape library.** GSAP's MorphSVG and the open-source `flubber` both solve the mismatch by resampling one path to match the other's point count. This is the honest way to morph arbitrary shapes, and the results are only as good as the correspondence the algorithm guesses.\n\nThe practical workflow: draw both shapes from the same base in Inkscape or Figma so the point counts already match, and the native route just works.\n\n## SMIL, and whether to use it\n\nSVG has its own animation elements (`<animate>`, `<animateTransform>`, `<animateMotion>`) known as SMIL. Chrome announced their deprecation in 2015 and reversed it; they work today in Chrome, Firefox and Safari and are not going away in practice.\n\nThey have one genuine advantage: an SVG file animated with SMIL animates when opened on its own, in an `<img>` tag, or as a CSS background image, where CSS-in-the-page cannot reach it. If you need a self-contained animated SVG, SMIL is the only way inside the format.\n\nTwo reasons to prefer CSS or JavaScript in a page: SMIL does not honour `prefers-reduced-motion` without extra work, and its timing model is a separate thing to learn for no gain when you already have the platform's.\n\n`<animateMotion>` deserves a mention: it moves an element along an arbitrary path, which CSS only gained recently through `offset-path`. If you need something to follow a curve, `offset-path: path(\"M ...\")` with an animated `offset-distance` is the modern equivalent and it is compositor-friendly.\n\n## The transform trap\n\nThe most common SVG animation bug: an element rotates around the wrong point.\n\nCSS `transform-origin` on an HTML element defaults to the element's centre. On an SVG element it is resolved against the element's own bounding box in user units, and `transform-box` decides which box. The fix is one line:\n\n```css\n.gear { transform-box: fill-box; transform-origin: center; }\n```\n\nWithout it, a gear in the middle of a 1000-unit viewBox rotates around the viewBox origin and swings across the screen. Most \"my SVG animation flies off\" questions are this.\n\n## Size and performance\n\nSVG is text, so it gzips extremely well: a 30KB SVG is often 4KB on the wire. Keep it small by cleaning the exported file: Illustrator and Figma both emit enormous amounts of redundant markup. **SVGO** removes it, typically cutting 40 to 70 per cent, and it is free.\n\nBe careful with filters. `feGaussianBlur` and friends are rasterised per frame and are among the slowest things a browser does. An animated blur on a large SVG will drop frames on hardware where the same animation with no filter runs cleanly.\n\n## The free path\n\n- **Inkscape**: a full SVG editor, open source, and unusually it edits the actual SVG rather than an internal model it exports from.\n- **SVGO**: MIT-licensed optimiser, runs as a CLI or a build step.\n- **Glaxnimate**: draws and animates SVG and Lottie together.\n- **flubber**: MIT, path interpolation for morphs.\n- **GSAP**: free for all uses now, including MorphSVG and DrawSVG.\n\n## The limitation\n\nSVG animation scales badly with element count in exactly the way Lottie's SVG renderer does, and for the same reason: each shape is a DOM node and the browser does style and layout work on all of them. A hundred animated nodes is comfortable; a thousand is not, and the fix is to move to canvas, at which point you have given up the thing that made SVG attractive.\n\nIt is also bad at anything photographic. An SVG containing a traced photograph is thousands of paths, larger than the JPEG, and slow. The format is for drawings, and it is excellent at drawings."},"check":{"question":"Why does setting pathLength=\"100\" on an SVG path make a self-drawing line animation easier to write?","options":[{"text":"It resamples the path to exactly 100 points, so any two paths with the attribute can be morphed into each other without a resampling library.","correct":false,"tempting":"Confuses the dash-arithmetic normalisation with point-count matching, which is what morphing actually requires."},{"text":"It caps the animation at 100 discrete steps, which prevents the browser from interpolating sub-pixel dash offsets and removes the shimmer on diagonal strokes.","correct":false,"tempting":"Invents a quantisation effect; the attribute changes the units the dash arithmetic uses, not how the stroke is rasterised."},{"text":"All dash arithmetic on that path is then expressed in units of a hundredth of its length, so the CSS can use 100 without measuring the real geometry.","correct":true},{"text":"It tells the browser to precompute the path's geometry at parse time and cache it, so getTotalLength no longer forces a synchronous layout when it is called from JavaScript during the animation's first frame.","correct":false,"tempting":"Describes a plausible performance optimisation that the attribute does not perform; its effect is purely on the units used for dash and offset values."}]},"prev":{"slug":"lottie-and-its-limits","title":"What Lottie carries and what it drops","url":"https://addaly.com/learn/motion-and-audio/lottie-and-its-limits"},"next":{"slug":"gif-is-not-a-video-format","title":"Why a GIF costs twenty times what a video costs","url":"https://addaly.com/learn/motion-and-audio/gif-is-not-a-video-format"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'Animating a drawing that is also a document', in Motion, Sound and Music. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/motion-and-audio/svg-animation (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"Animating a drawing that is also a document","courseTitle":"Motion, Sound and Music","module":{"index":3,"slug":"shipping-the-motion","title":"Shipping the motion"},"answer":null,"keyIdea":"SVG elements sit in the DOM, so CSS and JavaScript animate them directly, and the two techniques worth memorising are stroke-dashoffset for a self-drawing line, normalised with pathLength, and transform-box: fill-box to stop a rotation swinging across the viewBox.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"svg-is-a-document-which-is-the-whole-trick","runs":[{"text":"SVG is a document, which is the whole trick"}]},{"t":"p","runs":[{"text":"An SVG is markup. Every shape in it is an element with attributes, sitting in the same DOM as the rest of your page. That means the entire CSS and JavaScript animation toolkit applies to it directly: no runtime, no plugin, no export step. A designer draws a path in Inkscape, you give it a class, and CSS animates it."}]},{"t":"p","runs":[{"text":"This is why SVG remains the right answer for icons, logos, diagrams, line illustrations and data graphics, despite Lottie existing."}]},{"t":"h2","id":"the-line-that-draws-itself","runs":[{"text":"The line that draws itself"}]},{"t":"p","runs":[{"text":"The single most useful SVG technique, and it looks like magic until you know the mechanism."}]},{"t":"p","runs":[{"text":"A stroked path can have a dash pattern: "},{"text":"stroke-dasharray: 10 5","code":true},{"text":" means 10 units of ink, 5 of gap, repeating. Set the dash length equal to the "},{"text":"entire length of the path","i":true},{"text":" and the gap equal to it too, and you have one dash covering the whole line and one gap after it. Then "},{"text":"stroke-dashoffset","code":true},{"text":" slides that pattern along the path. Offset it by the full length and the ink is pushed off the end: the line is invisible. Animate the offset back to zero and the ink slides into place, which reads as the line being drawn."}]},{"t":"code","lang":"css","label":"CSS","text":".signature path {\n  stroke-dasharray: 1000;\n  stroke-dashoffset: 1000;\n  animation: draw 2s ease-out forwards;\n}\n@keyframes draw { to { stroke-dashoffset: 0; } }"},{"t":"p","runs":[{"text":"The number 1000 has to be at least the path’s length. Get the real one with "},{"text":"path.getTotalLength()","code":true},{"text":" in JavaScript, or, much better, put "},{"text":"pathLength=\"100\"","code":true},{"text":" on the path itself. That attribute tells SVG to pretend the path is 100 units long for all dash arithmetic, whatever its real geometry, so your CSS can use 100 everywhere and never needs measuring. Almost nobody knows about "},{"text":"pathLength","code":true},{"text":" and it removes the one awkward step in the technique."}]},{"t":"h2","id":"morphing-one-shape-into-another","runs":[{"text":"Morphing one shape into another"}]},{"t":"p","runs":[{"text":"Two routes, with a sharp limitation on both."}]},{"t":"p","runs":[{"text":"Animating the ","b":true},{"text":"d","b":true,"code":true},{"text":" attribute.","b":true},{"text":" Modern browsers can interpolate a path’s "},{"text":"d","code":true},{"text":" between two values, in CSS or the Web Animations API. It only works when both paths have the same number of segments, of the same types, in the same order. A four-point square morphs to a four-point diamond; a square does not morph to a circle unless you build the circle out of the same four cubic segments."}]},{"t":"p","runs":[{"text":"A shape library.","b":true},{"text":" GSAP’s MorphSVG and the open-source "},{"text":"flubber","code":true},{"text":" both solve the mismatch by resampling one path to match the other’s point count. This is the honest way to morph arbitrary shapes, and the results are only as good as the correspondence the algorithm guesses."}]},{"t":"p","runs":[{"text":"The practical workflow: draw both shapes from the same base in Inkscape or Figma so the point counts already match, and the native route just works."}]},{"t":"h2","id":"smil-and-whether-to-use-it","runs":[{"text":"SMIL, and whether to use it"}]},{"t":"p","runs":[{"text":"SVG has its own animation elements ("},{"text":"<animate>","code":true},{"text":", "},{"text":"<animateTransform>","code":true},{"text":", "},{"text":"<animateMotion>","code":true},{"text":") known as SMIL. Chrome announced their deprecation in 2015 and reversed it; they work today in Chrome, Firefox and Safari and are not going away in practice."}]},{"t":"p","runs":[{"text":"They have one genuine advantage: an SVG file animated with SMIL animates when opened on its own, in an "},{"text":"<img>","code":true},{"text":" tag, or as a CSS background image, where CSS-in-the-page cannot reach it. If you need a self-contained animated SVG, SMIL is the only way inside the format."}]},{"t":"p","runs":[{"text":"Two reasons to prefer CSS or JavaScript in a page: SMIL does not honour "},{"text":"prefers-reduced-motion","code":true},{"text":" without extra work, and its timing model is a separate thing to learn for no gain when you already have the platform’s."}]},{"t":"p","runs":[{"text":"<animateMotion>","code":true},{"text":" deserves a mention: it moves an element along an arbitrary path, which CSS only gained recently through "},{"text":"offset-path","code":true},{"text":". If you need something to follow a curve, "},{"text":"offset-path: path(\"M ...\")","code":true},{"text":" with an animated "},{"text":"offset-distance","code":true},{"text":" is the modern equivalent and it is compositor-friendly."}]},{"t":"h2","id":"the-transform-trap","runs":[{"text":"The transform trap"}]},{"t":"p","runs":[{"text":"The most common SVG animation bug: an element rotates around the wrong point."}]},{"t":"p","runs":[{"text":"CSS "},{"text":"transform-origin","code":true},{"text":" on an HTML element defaults to the element’s centre. On an SVG element it is resolved against the element’s own bounding box in user units, and "},{"text":"transform-box","code":true},{"text":" decides which box. The fix is one line:"}]},{"t":"code","lang":"css","label":"CSS","text":".gear { transform-box: fill-box; transform-origin: center; }"},{"t":"p","runs":[{"text":"Without it, a gear in the middle of a 1000-unit viewBox rotates around the viewBox origin and swings across the screen. Most “my SVG animation flies off” questions are this."}]},{"t":"h2","id":"size-and-performance","runs":[{"text":"Size and performance"}]},{"t":"p","runs":[{"text":"SVG is text, so it gzips extremely well: a 30KB SVG is often 4KB on the wire. Keep it small by cleaning the exported file: Illustrator and Figma both emit enormous amounts of redundant markup. "},{"text":"SVGO","b":true},{"text":" removes it, typically cutting 40 to 70 per cent, and it is free."}]},{"t":"p","runs":[{"text":"Be careful with filters. "},{"text":"feGaussianBlur","code":true},{"text":" and friends are rasterised per frame and are among the slowest things a browser does. An animated blur on a large SVG will drop frames on hardware where the same animation with no filter runs cleanly."}]},{"t":"h2","id":"the-free-path","runs":[{"text":"The free path"}],"role":"free"},{"t":"ul","items":[[{"text":"Inkscape","b":true},{"text":": a full SVG editor, open source, and unusually it edits the actual SVG rather than an internal model it exports from."}],[{"text":"SVGO","b":true},{"text":": MIT-licensed optimiser, runs as a CLI or a build step."}],[{"text":"Glaxnimate","b":true},{"text":": draws and animates SVG and Lottie together."}],[{"text":"flubber","b":true},{"text":": MIT, path interpolation for morphs."}],[{"text":"GSAP","b":true},{"text":": free for all uses now, including MorphSVG and DrawSVG."}]]},{"t":"h2","id":"the-limitation","runs":[{"text":"The limitation"}],"role":"limit"},{"t":"p","runs":[{"text":"SVG animation scales badly with element count in exactly the way Lottie’s SVG renderer does, and for the same reason: each shape is a DOM node and the browser does style and layout work on all of them. A hundred animated nodes is comfortable; a thousand is not, and the fix is to move to canvas, at which point you have given up the thing that made SVG attractive."}]},{"t":"p","runs":[{"text":"It is also bad at anything photographic. An SVG containing a traced photograph is thousands of paths, larger than the JPEG, and slow. The format is for drawings, and it is excellent at drawings."}]}],"check":{"question":[{"text":"Why does setting pathLength=”100” on an SVG path make a self-drawing line animation easier to write?"}],"options":[{"text":[{"text":"It resamples the path to exactly 100 points, so any two paths with the attribute can be morphed into each other without a resampling library."}],"correct":false,"tempting":[{"text":"Confuses the dash-arithmetic normalisation with point-count matching, which is what morphing actually requires."}]},{"text":[{"text":"It caps the animation at 100 discrete steps, which prevents the browser from interpolating sub-pixel dash offsets and removes the shimmer on diagonal strokes."}],"correct":false,"tempting":[{"text":"Invents a quantisation effect; the attribute changes the units the dash arithmetic uses, not how the stroke is rasterised."}]},{"text":[{"text":"All dash arithmetic on that path is then expressed in units of a hundredth of its length, so the CSS can use 100 without measuring the real geometry."}],"correct":true,"tempting":null},{"text":[{"text":"It tells the browser to precompute the path’s geometry at parse time and cache it, so getTotalLength no longer forces a synchronous layout when it is called from JavaScript during the animation’s first frame."}],"correct":false,"tempting":[{"text":"Describes a plausible performance optimisation that the attribute does not perform; its effect is purely on the units used for dash and offset values."}]}],"order":[1,2,0,3],"why":null},"prev":{"slug":"lottie-and-its-limits","title":"What Lottie carries and what it drops","minutes":10},"next":{"slug":"gif-is-not-a-video-format","title":"Why a GIF costs twenty times what a video costs","minutes":9}}}