How to make animated loading graphics with only SVG

By Matt Visiwig Matt Visiwig Portrait Aug 25, 2023

I spent a week creating a bunch of animated SVG preloaders. A preloader is an animation, which generally loops, shown while something is loading rather than a blank screen. It gives users confidence that they’re waiting for a reason. Another common preloader is a progress bar that shows the percentage completed of whatever’s loading.

Today we’re going to work with SVG and nothing else. No CSS or JS is required.

Once you know the basics of making animated SVG preloaders, it’s pretty easy to make variations. Therefore, let’s create the simplest of all the preloaders: the spinner.

A spinner literally rotates *dramatic pause* and keeps rotating.

SVG Animation basics

First we need a graphic. I have a circle with a stroke on only half of it. I use a stroke-dasharray to achieve this, but there are multiple ways to make this graphic, that’s not super important. You just need any shape or element that can spin.

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'>
   <circle fill='none' stroke='#FF156D' stroke-width='15' stroke-linecap='round' stroke-dasharray='230 1000' stroke-dashoffset='0' cx='100' cy='100' r='70'></circle>
</svg>

That brings us to the animated part. To make the graphic spin, we need to manipulate the rotation. We’ll add the attribute transform="" to the circle and give it a value of rotate(X) where the X represents the degree of the rotation.

By default elements have no rotation or a rotation of 0 degrees. If we want it to spin, we’d keep increasing the rotation. At 180 degrees, your element is upside down. To make it spin one full rotation, we need to reach 360 degrees which looks exactly like the original orientation. That’s convenient because if we start the animation at 0 degrees, increase that number, and end at 360 degrees: The start and end state will look the same. That trick allows us to loop the same animation over and over.

To make the actual animation change the rotation, we can use SVGs native animation language SMIL and use the <animateTransform> tag nested in the element we want to rotate. In our case the element is our circle.

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'>
   <circle transform='rotate(0)' transform-origin='center' fill='none' stroke='#FF156D' stroke-width='15' stroke-linecap='round' stroke-dasharray='230 1000' stroke-dashoffset='0' cx='100' cy='100' r='70'>
      <animateTransform 
         attributeName='transform'
         type='rotate'
         from='0'
         to='360'
         dur='2'
         repeatCount='indefinite'>
      </animateTransform>
   </circle>
</svg>

Let’s break down each attribute.

Basic animation attributes

  • attributeName tells us which attribute we’ll be animating
  • type tells us the transform type, in this case rotation
  • from is the starting value of the rotation
  • to is the end value of the rotation
  • dur is the duration of this animation
  • repeatCount is how many times the animation will loop

There are other animation attributes, and they can help you achieve more complex animations. But for now let’s appreciate how powerful the spinning animation can be. Let’s take this same animation, and place it inside a RECT element to make a rotating square.

I didn’t touch the animation, it’s exactly the same. The only thing I changed is the graphic the animation tag is embedded within.

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'>
   <rect transform='rotate(0)' transform-origin='center' fill='#808' width='100' height='100' x='50' y='50'>
      <animateTransform 
         attributeName='transform'
         type='rotate'
         from='0'
         to='360'
         dur='2'
         repeatCount='indefinite'>
      </animateTransform>
   </rect>
</svg>

One thing I snuck into both the rotating circle and square animation is cause of much frustration. It’s the attribute transform-origin='' and I gave it a value of center. The rotation would spin around the default transform-origin‘s coordinates (0, 0), and would result in this unwanted animation:

(0, 0)

A few things to note: you can place transform-origin on the SVG tag or a specific element. You can even specify the rotation coordinates like so: transform='rotate(180 75 100)' where this would rotate 180 degrees around the point (75, 100). CSS can set the transform-origin and transform-box, so if something isn’t behaving as expected these could be responsible. I won’t go into detail for this tutorial, but if you run into a snag, you now have a few things to research.

Animating other SVG attributes

If you know anything about CSS animation, there are a few properties that are easy to manipulate: color, opacity, scale, translate and rotation. That’s the case with SVG too, however, you can animate nearly any attribute if it has some form of numeric-like values, including hex codes and paths. Note that there are a few types of animation tags:

  • <animate> to animate most attributes
  • <animateTransform> to manipulate a transform attribute, which can animate rotate, scale, translate, or skew
  • <animateMotion> to animate an element moving along a path

We started with rotation which is a type of transform, thus we used a <animateTransform>. If you wanted to manipulate the other transforms, you’d simply change the type="rotate" to type="scale" or type="translate" and then the to and from attributes would apply to those transform type values. Below I’m now animating a circle’s scale from 1 (original size) to 0.5 (half the size).

You might notice how it smoothly animates smaller, but jumps back to the big circle. With the rotation, the start and end state were visually the same (0 and 360) allowing for a seamless loop. But with scale, 1 and 0.5 are noticeably different start and end states, thus the jump in size.

This is why the FROM and TO values are limited, as it only allows us to set the initial and end state.

Animating between the start and end state

Instead of FROM and TO, we’ll want to use the the attribute VALUES. It allows us to set a series of values by separating the values with a ; (semi-colon).

In our previous example if we want to animate between 1 and .5 and back to 1, we’d have values="1; 0.5; 1".

How does the browser know how much time to give to each transition between values? By default, it will divide the duration in equal parts based on the number of transitions between values. I simply count the semi-colons, 2 in this case. If the duration is 6 seconds, each half of the animation would get a transition of 3 seconds by default. We could override this split by setting calcMode="spline" and adding the attributes keyTimes or keySplines.

keySplines

keySplines is the attribute for setting easing. It’s a bit more complex then CSS where you can just use keywords like “linear” and “ease-in”. However, if you’ve ever wanted more control over the easing, you may have used cubic-bezier(0.42, 0, 0.58, 1). keySplines is not much different. In the previous example, we had two semi-colons in the values attribute, so we’ll need two easing values to match like so: keySplines="0 0 1 1;0 0 1 1" which is separated by a semi-colon. I use a keySpline generator to help pick the easing values I want.

keyTimes

keyTimes is an attribute to divvy up the transition timing between values. For instance, if we want the circle to shrink slowly, we could allocate that part 80% of the animation duration. Then give the remaining 20% to the portion where the animation transitions back to the initial state. Here’s what that would look and the code used:

<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'>
   <circle transform='rotate(0)' transform-origin='center' fill='#808' cx='100' cy='100' r='100'>
      <animateTransform
         attributeName='transform'
         type='scale'
         values='1;.5;1'
         keyTimes="0; 0.8; 1"
         dur='2'
         repeatCount='indefinite'>
      </animateTransform>
   </circle>
</svg>

The keyTimes="0; 0.8; 1" is making the deflation part happen slowly and the inflation part happen rapidly. With keyTimes the first and last value always need to be 0 and 1 respectively, declaring the initial and end state. The number of values should match the values attribute, that’s why we have 3 values in both the values and keyTimes attributes.

Animating multiple attributes at the same time

Up to this point, we animated one attribute of a single element. To manipulate more attributes, you simply can add more animate tags targeting different attributes.

If you want the animations to work on the same timeline, you have to ensure they share the same duration or multiples of the same duration.

Check out this example:

There’s two animations happening to the square. It’s obvious that it is being animated up and down, which uses the Y attribute. You can also see the height of the square shrinks as it bounces. By manipulating the Y coordinate and height of the <rect> element while using the same duration, we keep the two separate animations in sync.

This is powerful. I’ve used this concept to animate gradients by targeting stop-color and to make other cool animated backgrounds.

Creating your own preloaders

If you want to get more inspiration, visit the animated SVG preloaders I released for free. Click to export any of them and inspect the code. You might pick up on some cool tricks, such as animating strokes along a path or how to stagger the elements with the begin attribute.

I also plan to release a YouTube video dissecting the code. If I forget to link it, you can find it on youtube.com/@MattVisiwig.

Matt Visiwig Headshot

Hey, I'm Matt , the creator behind SVG Backgrounds. I produce free and paid resources every month, sign up for alerts.


Get freebies See latest releases