# Making SVG move

> Choose the right animation engine for where an SVG lives, draw a line on with two CSS properties, and stop rotations from orbiting the corner.

Module: SVG and icons · Level: intermediate · 8 min · https://school.agents.org.in/visual/svg/animating-svg

Press replay. Then grab the slider and drag it yourself.

*[Interactive demo: The drawing animation is one property moving from 1 to 0. The slider moves the same property by hand.]*

That line is not being drawn. It is already all there. It is hidden behind one very long dash, and the dash is sliding out of the way. That trick, and a handful like it, covers most of the SVG animation you will ever need.

## First question: where does the file live?

Before easing, before duration, ask where the SVG renders. That decides which tools can even reach it.

| Where it renders | What can animate it |
|---|---|
| Inline in the page (React, HTML), reacting to clicks and state | CSS transitions or the Web Animations API |
| Inline, but driven by a drag, a spring or a long timeline | Motion or GSAP, whichever the project already uses |
| Loaded as `<img>`, a background image, in an email, on social, in Figma | Only what is *inside the file*: CSS `@keyframes` in its own `<style>`, or SMIL (Animation written as tags inside the SVG file itself (<animate>, <animateMotion>). It keeps playing even when the SVG is loaded as a plain image, where page CSS and JavaScript cannot reach) tags |
| A rich After Effects export that already needs a player | Lottie (A format that plays After Effects animations in apps and on the web through a JavaScript player. Handy for rich exports, but usually several times larger than the same motion as plain SVG) or Rive, and only then |

The third row catches people out. An SVG loaded as an image is sealed: your page's CSS and JavaScript can't touch it. Animation has to travel inside the file. CSS keyframes are the most portable choice; SMIL still wins for moving something along a path with `<animateMotion>`.

> **Usually smaller than the Lottie.** Native SVG with CSS or SMIL is often five to ten times smaller than the Lottie export of the same motion, and it keeps the `<title>` a screen reader needs. Lottie earns its place when the animation is genuinely rich.

## Drawing a line on

The trick in the demo uses stroke-dasharray (The SVG setting that turns a line into dashes. Make one dash as long as the whole line, slide it with stroke-dashoffset, and the line looks like it is being drawn), which turns a stroke into dashes, and `stroke-dashoffset`, which slides them along.

Measuring a path's length is fiddly, so skip it. Set `pathLength="1"` on the path and the browser treats the whole line as one unit long, whatever its real size.

```css
path {
  stroke-dasharray: 1;        /* one dash, as long as the whole path */
  stroke-dashoffset: 1;       /* pushed fully out of view */
  stroke-linecap: round;      /* soft ends while it draws */
  animation: draw 1600ms cubic-bezier(0.65, 0, 0.35, 1) forwards;
}
@keyframes draw { to { stroke-dashoffset: 0; } }
```

Drawing is one of the few places an ease-in-out curve is right: the line is moving across the screen on its own, not answering a click.

## The corner problem

Now press rotate, a few times.

*[Interactive demo: Same rotate, same gear. On the left it swings around the top-left corner of the drawing (the red dot). On the right it turns in place.]*

In HTML, a rotation turns around the element's center. In SVG, it turns around **(0, 0)**, the top-left corner of the whole drawing. So every rotation and scale you copy from HTML orbits the corner.

The fix is two lines. transform-box (A CSS setting that decides what a transform measures from. In SVG the default is the corner of the whole drawing, so set transform-box: fill-box to rotate or scale a part around its own center) tells the browser to measure from the shape's own box, and then `transform-origin` means what you expect.

```css
.gear { transform-box: fill-box; transform-origin: center; }
.arm  { transform-box: fill-box; transform-origin: 10% 20%; } /* the shoulder */
```

For a mascot, put the origin where the part would hinge in life: a shoulder, a wrist, a lid.

*(Most "my SVG animation goes haywire" bugs are this one.)*

## Keep it cheap

The rules from motion apply unchanged. `transform` and `opacity` are cheap: the browser moves a finished picture. Animating `d`, `points`, `r`, `stroke-width` or a filter makes it repaint every frame. Fine for one small icon; **ruinous for a whole scene**.

- **Move groups, not paths.** Animate `<g id="arm">`, `<g id="eyes">`, not three hundred separate paths.
- **Stagger by 30 to 80ms.** Paired parts (two eyes, two arms) share duration and easing.
- **Budget the file.** About 2 KB for an animated icon, 10 to 30 KB for a hero mascot. Reuse shapes with `<use>`, move them with transforms instead of redrawing.

## Reduced motion travels with the file

Some people turn on "reduce motion" in their system settings because movement makes them unwell. Inline SVG respects it only if you wrote the media query. A self-contained file can carry its own:

```css
@media (prefers-reduced-motion: reduce) {
  .gear, path { animation: none; }
}
```

That query works even inside a file loaded as `<img>`. SMIL can't read it, so a SMIL-only file needs a still fallback image.

A copied animated icon with its own keyframes and no reduced-motion query is the most common miss in generated UI. Check every one.

## Ask for it precisely

**Animate an SVG (paste into your agent):**

```text
Animate this SVG. It is used as <img> in our marketing page, so the animation must live inside the file.
- CSS @keyframes in the file's own <style>, selectors scoped under the root id
- draw the outline on with pathLength="1", stroke-dasharray: 1, stroke-dashoffset 1 → 0, round linecaps
- the lid rotates around its hinge: transform-box: fill-box with transform-origin at the hinge
- only transform, opacity and stroke-dashoffset animate
- @media (prefers-reduced-motion: reduce) shows the finished drawing
- keep it under 10 KB; report the final size
```

**Check yourself:** A logo SVG ships as an <img> tag on your landing page. Your agent added the animation to the page's global CSS file. What happens?

- [ ] It works, but only in Chrome
- [x] Nothing animates: page CSS can't reach inside an SVG loaded as an image
- [ ] It animates, but ignores reduced motion
- [ ] It animates twice as fast

*Why:* An SVG in an <img> is sealed off from the page. The animation has to live inside the file, as CSS keyframes in its own <style> or as SMIL.

## In the skill

- https://school.agents.org.in/skill/svg/svg-animation.md
