How to animate an SVG with CSS: draw, fill and move
Animate an SVG with CSS keyframes and transitions: the line that draws itself with stroke-dashoffset, fill and colour changes, rotation, and common faults.
Published
CSS animates an SVG the same way it animates HTML: with transition for a change between two states and @keyframes for anything longer. The shapes’ fill, stroke, opacity, transform and dash properties can all be animated. The one condition is that the CSS must be able to reach the shapes, which means the SVG is inline in the page, or the CSS is written inside the SVG file in a <style> element.
Where the CSS has to be
| How the SVG is placed | CSS in the page | CSS inside the SVG file | Hover and clicks |
|---|---|---|---|
Inline <svg> in the HTML | Works | Works | Work |
<img src="file.svg"> | Cannot reach the shapes | Works | Do not work |
<object> | Cannot reach the shapes | Works | Work |
| CSS background | Cannot reach the shapes | Plays in current browsers | Do not work |
An animation written inside the file travels with it and plays in an <img>. An animation that responds to the page, such as a hover on a button, needs inline SVG. SVG in HTML compares the placements.
The line that draws itself
This is the best known SVG effect, and it is a trick with dashes. Give the line one dash as long as the whole path, slide the dash off the path with stroke-dashoffset, then animate the offset back to zero. This is a complete file:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<style>
.line {
fill: none;
stroke: teal;
stroke-width: 4;
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: draw 2s ease-in-out forwards;
}
@keyframes draw {
to { stroke-dashoffset: 0; }
}
</style>
<path class="line" pathLength="100" d="M10 80 Q50 10 90 80"/>
</svg>
The curve is drawn from left to right over two seconds and stays.
How it works:
pathLength="100"tells the browser to treat the path as 100 units long for dash purposes, whatever its real length. Without it, the dash would have to match the true length, which the browser reports frompath.getTotalLength()in the console.stroke-dasharray: 100makes a dash of 100 followed by a gap of 100: one dash covering the whole line.stroke-dashoffset: 100slides the pattern along by 100, so the gap sits over the line and nothing shows.- The animation brings the offset to 0, pulling the dash back over the line from its starting point.
forwardskeeps the final state. Without it the line vanishes again when the animation ends.
The line is drawn in the direction the path was written. To draw from the other end, start the offset at -100. SVG fill and stroke explains dashes, and SVG path the d attribute.
The effect needs strokes. Artwork made of filled shapes, such as lettering converted to outlines, will have its outlines traced, not its strokes written.
Fill and colour
A colour change on hover is a transition. This CSS is for an inline SVG:
.shape { fill: teal; transition: fill 0.3s; }
.shape:hover { fill: crimson; }
A repeating change is a keyframe animation:
.shape { fill: teal; animation: pulse 2s infinite; }
@keyframes pulse {
50% { fill: gold; }
}
The colour runs from teal to gold and back every two seconds.
To draw an outline and then flood it with colour, run two animations one after the other. The second waits for the first with a delay:
.logo path {
fill: teal;
fill-opacity: 0;
stroke: teal;
stroke-dasharray: 100;
stroke-dashoffset: 100;
animation: draw 2s forwards, fill-in 0.5s 2s forwards;
}
@keyframes fill-in {
to { fill-opacity: 1; }
}
This reuses the draw keyframes from the first example. Each path needs pathLength="100".
A shape that seems to fill up like a glass is a different trick: a coloured rectangle is clipped to the shape’s outline, and the rectangle is moved upwards.
Moving, turning and scaling
transform animates smoothly. The catch in SVG is the point the shape turns about. By default it is the origin of the drawing’s coordinates, the point 0,0, which is usually the top-left corner, so a rotated shape swings round in a wide arc. Two lines fix it:
.spinner {
transform-box: fill-box;
transform-origin: center;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
transform-box: fill-box makes the shape’s own bounding box the reference, and transform-origin: center then means the centre of the shape.
A second catch: a CSS transform replaces a transform attribute on the same element. A shape that was positioned with the attribute jumps to its untransformed place when the animation starts. Leave the attribute on a surrounding <g> and animate the shape inside, or the other way round. SVG transform covers the attribute.
What CSS cannot animate dependably
CSS animates properties. CSS cannot animate the points of a polygon at all, and the d of a path only in some browsers. For one shape changing into another, use JavaScript, or SVG’s own animation elements such as <animate>, which sit inside the file and need no CSS.
Respect reduced motion
Some people set their device to reduce motion. A media query switches the animation off for them and shows the finished state:
@media (prefers-reduced-motion: reduce) {
.line { animation: none; stroke-dashoffset: 0; }
}
Testing
The SVG viewer shows a file as an image, the same way an <img> tag does. Paste a file with its own <style> block and the animation should play. If it plays there and not on the page, and the viewer shows no warning about xmlns, the page is at fault. Hover effects will not respond there, for the reason in the table above.
Common problems
| What you see | Cause | Fix |
|---|---|---|
| Nothing moves | The SVG is in an <img> and the CSS is in the page | Move the CSS into the file’s <style>, or inline the SVG |
| Only part of the line is drawn, or it shows as dashes | The dash is shorter than the path | Set pathLength on the path, or use its true length |
| The line is drawn, then disappears | The animation returns to its start | Add forwards |
| The line is drawn from the wrong end | The path runs the other way | Start stroke-dashoffset at the negative value |
| A rotation swings round the corner of the image | The shape turns about the drawing’s point 0,0 | transform-box: fill-box and transform-origin: center |
| The shape jumps when the animation starts | The CSS transform replaced a transform attribute | Put one of the two on a wrapping <g> |
| Hover does nothing | The SVG is in an <img> or a background | Inline it |
| A hover colour is ignored | The shape has its own inline style | See changing SVG colour with CSS |
| All the paths draw at once | They share one animation | Give each an animation-delay |
Questions
Does an animated SVG work in an img tag? Yes, if the animation is inside the file and does not depend on scripts, hover or outside files.
Is CSS or JavaScript better for SVG animation? CSS for simple, self-contained effects: drawing, fading, spinning, colour changes. JavaScript when steps must be timed against each other, respond to scrolling, or change a shape’s geometry.
Do filled shapes need converting for the drawing effect? The effect follows strokes. For handwriting that appears to be written, the artwork needs single-line paths along the middle of each letter, not outlines.