Shots in this scene
SVG for Motion
In one line
An SVG is a drawing with its own coordinates. CSS can animate its transforms, colours and strokes, but a shape turns around the top-left corner of the drawing unless you set transform-box: fill-box or a pivot point.
Think of it like this
A paper cut-out pinned to a board. Pin it through the middle and it spins in place. Pin it in the top-left corner of the board and it swings around that corner, maybe right off the board. SVG pins every shape in the top-left corner of the whole drawing unless you say otherwise.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- viewBox
- The window onto an SVG drawing:
min-x min-y width heightin user units. The browser scales it to the size you give the<svg>. - user unit
- One step in the drawing's own coordinates. In SVG transforms, a CSS
pxmeans a user unit. - transform-origin
- The point a transform turns or scales around. In SVG it starts at the top-left corner of the drawing.
- transform-box
- Which box the origin refers to:
view-box(the whole drawing, the SVG default),fill-box(the shape's own box) orstroke-box(the box with its stroke). - presentation attribute
- A style written as an attribute, like
transform="rotate(45)"orfill="red". A CSS rule for the same property wins over it. - <use>
- An element that draws a copy of another shape. The copy lives in a hidden tree, which outside CSS selectors cannot reach.
Step by step
01Run the three squares and read the console
"Three squares, one quarter turn" prints the origin and box of each. The blue one says origin 0px 0px, box view-box; the orange one origin 20px 20px, box fill-box; the purple one origin 260px 60px, box view-box. Three animations, one per square.
02Slow it down and watch the blue one leave
Press Replay, then Pause and speed 0.1×. At the middle of the turn all three tracks show 45 degrees: the pictures differ only because the pivot differs. At the end the blue square is gone, because a quarter turn around the top-left corner moved its centre to x = -60.
03Read the use example
"Three copies of one gem" counts three <use> elements and two animations: pulse on rect#gem and spin on use#u2. Three gems pulse at once, yet only one pulse track exists.
04Find the gem that spins in place
Step through the middle gem's turn: it spins on its own centre, because translate: 130px 40px places it and the origin 15px 15px is its centre.
Try it yourself
- 1
Take away fill-box
In the first example change
#b { transform-box: fill-box; transform-origin: center; }to#b { transform-origin: center; }and press Replay. The orange square no longer turns in place: it circles the centre of the drawing and ends 10 units lower and 10 to the left, centred on (150, 70). The console now printsbox view-boxfor it. - 2
Move the pivot by hand
Change the purple square's
transform-origin: 260px 60pxto240px 40px(its own top-left corner). It now turns like a door on a hinge: at 600 ms that corner has not moved. - 3
Count the tracks of the gems
In the second example pause and click the
spintrack:CSSAnimation, 2000 ms, linear, infinite,rotateonuse#u2. There is no track for the gem on the left or the right, although they pulse too.
Code & diagrams
Same keyframes on three squares. Pause at 0.1× and step: the blue square swings around the drawing's top-left corner and leaves the picture.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
a: origin 0px 0px, box view-box
b: origin 20px 20px, box fill-box
c: origin 260px 60px, box view-box
animations: 3The pulse is defined once on the source and runs separately in each copy, offset by `--d`. The timeline lists the source only.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
Console
Nothing logged yet.Expected console output
copies drawn: 3
animations the page can see: 2
pulse on rect#gem
spin on use#u2
gem's own delay: 0sBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Style a copy from outside
In the second example add #u1 rect { fill: red; } to the CSS.
Break #2
Position a spinning copy with x and y
In the second example change the middle copy to <use id="u2" href="#gem" x="130" y="40" class="spin" /> and the keyframes to @keyframes spin { to { transform: rotate(360deg); } }.
Myth vs fact
Myth
transform-origin: center turns an SVG shape around its centre, as in HTML.
Fact
Only with transform-box: fill-box. Without it, center means the centre of the whole drawing (measured: the square circled it).
Myth
A CSS transform adds to the transform attribute.
Fact
It replaces it (measured).
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
fill-boxis the shape's geometry without its stroke. Usestroke-boxwhen a thick stroke matters for where the centre or edge should be. - ▸
A
transformorrotateon an SVG shape measured as composite, like on an HTML box. Read the track colour for your own drawing, then check DevTools' Paint flashing (Topic 6.5). - ▸
<use>is how icon sprites work: one hidden<svg>with<symbol>elements and a<use href="#icon">per place. Animation on a sprite icon must be reachable through the<use>itself or custom properties, never through the symbol's inner selectors.
Remember this
- 1
The problem this solves: you draw a shape in SVG, add
transform: rotate(90deg)and the shape flies away instead of turning on the spot. Nothing is wrong with the browser. The turning point (thetransform-origin) means something different in SVG than in HTML, and this topic shows what, with measured numbers. - 2
An SVG has its own coordinates.
viewBox="0 0 300 120"is a window onto a drawing 300 units wide and 120 high. A user unit is one step in those coordinates. Thewidthandheighton the<svg>set the size on screen. Shown 600 px wide, one unit is 2 px. In an SVG, a CSSpxinside a transform means one user unit: we measured thattranslate(10px, 0)on a shape in a drawing shown at 2× moved it 20 screen pixels. The whole drawing scales together. - 3
What CSS can animate in an SVG. The same engine as for HTML:
transform,translate,rotate,scale,opacity,fill,stroke,stroke-width,stroke-dashoffset(Topic 13.2),offset-distance(13.3) andd(13.4). Chromium also lets CSS setcx,cy,r,x,y,widthandheight(CSS.supportssays yes). The cost table (measured in headless Chromium) saystransformandopacityare composite,fill,stroke,stroke-dashoffsetandoffset-distanceare paint (amber), andcx,cy,r,dandstroke-widthare layout (red: the geometry is recomputed every frame). Prefertransformto move a shape, and keep geometry morphs short. - 4
The transform-origin surprise. In HTML the turning point is the centre of the element's box. In SVG the default is the top-left corner of the drawing: we measured
0px 0px.transform-boxsays which box the origin refers to.view-boxis the default for SVG (the whole drawing).fill-boxis the shape's own box, without its stroke.stroke-boxadds the stroke. Withtransform-box: fill-box; transform-origin: centerthe origin of a 40-unit square measured20px 20px, its own centre. The other way is a point in the drawing's units, such astransform-origin: 260px 60px, which is right for a deliberate pivot like a wheel hub. - 5
**Forgetting
fill-boxstill goes wrong, only less.**transform-origin: centeralone, with the defaultview-box, means the centre of the whole drawing: the orange square then circled that point (tryIt 1). The blue square left the edge: a quarter turn around (0, 0) puts its centre at (-60, 60). - 6
Attribute or CSS, never both.
transform="rotate(45)"in the markup is a presentation attribute. A CSStransformon the same element replaces it, it does not add to it: an attributetranslate(50 0)plus a CSSrotate(10deg)measured a matrix with no translation left. The syntax differs too: the attribute takes unitless degrees and spaces, CSS needs units, and an invalid CSS value is ignored without a message. To keep a placement, put it on a wrapper<g transform="…">and animate the shape inside. - 7
**
<use>copies.**<use href="#gem">draws a shape defined once somewhere else, as many times as you like. The copies live in a hidden tree. We measured four facts. A rule on the source applies to every copy and each copy animates on its own:animation-delay: var(--d)with a different--don each<use>made three copies pulse out of step, because custom properties pass into the copy.document.getAnimations()listed only onepulse(on the source), so the timeline shows one track for three moving things. A selector outside cannot reach a copy (breakIt 1). Animating the<use>itself works, but itsxandyare applied inside it, sofill-boxmeasured15px 15px(breakIt 2). Place the copy with thetranslateproperty instead, as the second example does.
Explain it without notes
Why does a rotated SVG shape sometimes leave the screen, and how do you fix it?
How do you give each <use> copy of one shape its own animation timing?
Practice
Make a star shape (#star) pulse (scale 1 to 1.3 and back) around its own centre, forever, and give it a reduced-motion rule.
Three <use> copies of #dot should blink one after another, 150 ms apart. Write the CSS and the markup.
Trade-offs
- ↔
<use>keeps the markup small, but the copies cannot be styled from outside and the timeline cannot list them. Draw the shape out when each copy needs its own selectors or tracks.
Done when you can
You can say where an SVG shape turns by default and fix it two ways
You can say what
fill-boxandview-boxmeanYou can predict that a CSS transform replaces the attribute
You can animate
<use>copies out of step and know what the timeline will show