Creating an animated SVG spinner

SVG and CSS were meant to be ♥. In this tutorial we will be learning how to draw a simple SVG shape and animate the stroke of that shape to create a loading spinner.

We will be writing in SCSS to streamline styling.

jsfiddle.net/codylg/v8670mj6

Drawing the SVG

The HTML/SVG code is simple; our goal is to draw a 40px wide circle. The only attributes here are the x and y coordinates (origin point) and the radius.

To account for the thickness of the stroke when we add it in later, the radius r will be 18 rather than 20.

<svg class="spinner">
<circle cx="20" cy="20" r="18"></circle>
</svg>
jsfiddle.net/codylg/v8670mj6/1

Styling the SVG

As always we want to define the box model first. Because we are working with SVG, this includes the viewBox and its x and y coordinates.

The viewBox defines the bounds to which our circle will be rendered.

$spinnerSize: 40;
svg.spinner {
width: $spinnerSize + px;
height: $spinnerSize + px;
x: 0px; y: 0px;
viewBox: 0 0 $spinnerSize $spinnerSize;
}

We will apply stroke styles and a transparent fill to the circle. This creates an outline around the circle path. We will be animating the stroke to create our spinner.

svg.spinner {
...
    circle {
fill: transparent;
stroke: #0ac8a6;
stroke-width: 4;
stroke-linecap: round;
}
}
jsfiddle.net/codylg/v8670mj6/2

The spinner is taking shape, but before animating we need to apply a few more styles. The ‘growing and retracting’ effect is created using a wide dash stroke. The stroke gets ‘pushed’ around the circle path by increasing the offset between dashes.

To create the wide dash stroke we will use the stroke-dasharray property. Setting it to π × radius creates a dash with length equal to the circumference of the circle. For now, this will appear as a solid stroke.

svg.spinner {
...
    circle {
...
stroke-dasharray: (3.14 * $spinnerSize);
}
}

Finally, we need to set the transform-origin and animation properties. Setting transform-origin to the center of the circle ensures it will rotate evenly. The animation property will use the spinner animation-name, which we will keyframe next.

svg.spinner {
...
    circle {
...
transform-origin: (0.5px * $spinnerSize) (0.5px * $spinnerSize) 0;
animation: spinner 4s linear infinite;
}
}

Keyframing the animation

Earlier we set the stroke to a dash with length equal to the circumference. Now we can animate the offset of this dash to ‘push’ it off the circle path.

The offset can be modified using the stroke-dashoffset property. Increasing the offset will retract the dash, while decreasing it will grow it back along the circle path.

@keyframes spinner {
0% {
stroke-dashoffset: (0.66 * $spinnerSize);
} 50% {
stroke-dashoffset: (3.14 * $spinnerSize);
} 100% {
stroke-dashoffset: (0.66 * $spinnerSize);
}
}
jsfiddle.net/codylg/v8670mj6/3

So, we have animated the stroke along the path of the circle — that was the hard part. But, as a loading spinner, the stroke retracting isn’t quite what we are after. We want it to look like there is constant progression — to create the illusion that the stroke is always moving ‘forward’.

How can we do this? By rotating the circle as the stroke animates.

@keyframes spinner {
0% {
...
transform: rotate(0deg);
} 50% {
...
transform: rotate(540deg);
} 100% {
...
transform: rotate(1080deg);
}
}
jsfiddle.net/codylg/v8670mj6/4

We are getting closer, but the spinner appears to slow down. This happens when the stroke is moving against the direction of rotation. We can counter this by rotating more in the first half of the animation.

@keyframes spinner {
0% {
...
transform: rotate(0deg);
} 50% {
...
transform: rotate(720deg);
} 100% {
...
transform: rotate(1080deg);
}
}

In the example below there are two spinners:

  • The first has the existing uniform rotation speed (doesn’t counter ‘slowdown’).
  • The second uses the faster rotation in the first half of the animation.

I have added some red markers as references for the speed at which the spinner is rotating.

jsfiddle.net/codylg/v8670mj6/6

You can see that the second spinner is much smoother. This is because it appears to move at a more uniform speed. To finish, let’s increase the speed by reducing the duration of the animation property.

jsfiddle.net/codylg/v8670mj6

Further reading


This post has also been published at tech.scrunch.com/blog