How to make animated loading graphics with only SVG

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='' 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>

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='' 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'>

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='' viewBox='0 0 200 200'>
   <rect transform='rotate(0)' transform-origin='center' fill='#808' width='100' height='100' x='50' y='50'>

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 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 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='' viewBox='0 0 200 200'>
   <circle transform='rotate(0)' transform-origin='center' fill='#808' cx='100' cy='100' r='100'>
         keyTimes="0; 0.8; 1"

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

3 ways to use SVG blobs on your website (CSS)

In the last tutorial I showed you how to manually make your own blob and quickly export it. We glossed over how to integrate it onto your website.

Today let’s focus on that last part and demonstrate three ways to use blobs in your designs.

So you made a blob in Figma, Illustrator or whatnot, and if you haven’t, you can quickly grab one from the many SVG blob generators out there. I’m grabbing one from my website > SVGs > Simple blobs, there’s some free ones.

SVG blob gallery

We’re going to:

  • Add a blob as a background
  • Mask an image with a blob
  • Divide two sections with a blob curve

Let’s get to it!

Adding a blob as a simple background

When you give your container a background color, it shows its rectangular boundaries which is looks boxy. Instead, if we place a blob as the background, it will look more organic and curvy.

The way to do that is with the CSS property background-image and the specify your image.svg as the value. Unfortunately with image files, you then have to upload your image to the server and type out the correct path.

Seeing as SVG is code, it’s quicker to paste a data URI as the image source instead. Link in the description for those who need to convert your inline SVG to a data URI.

/* Image file */
background-image: url(your-path/your-image.svg);

/* Data URI */
background-image: url('data:image/svg+xml,<svg xmlns="" viewBox="0 0 2000 1155" fill="%23808"><path d="M968.99 1033c-187.45.85-174.59 47.22-449 48-352 1-463-239-463-548 0-273.09 190-474 496-472 277.28 1.81 277.22 60.83 470 57 252-5 217.88-56.12 487-57 305-1 431 290 433 526s-117.08 502.4-432 507c-342 5-322-62-542-61Z"></path></svg>');

You’ll likely need to specify background-repeat: no-repeat because repeating the background is the default and we no longer live in the 90s.

We need a few other CSS properties to ensure the blob doesn’t get clipped. We can use background-size: contain so both the blob’s width and height are no bigger then the container. I’m also using background-position: center to center the blob.

I recommend giving the container some padding to help the content stay with in the blob.

background-image: url('data:image/svg+xml,<svg xmlns="" viewBox="0 0 2000 1155" fill="%23808"><path d="M968.99 1033c-187.45.85-174.59 47.22-449 48-352 1-463-239-463-548 0-273.09 190-474 496-472 277.28 1.81 277.22 60.83 470 57 252-5 217.88-56.12 487-57 305-1 431 290 433 526s-117.08 502.4-432 507c-342 5-322-62-542-61Z"></path></svg>');
background-repeat: no-repeat;
background-size: contain;
background-position: center;
padding: 30px;

Pro-tip: you can layer multiple backgrounds to make a composition with a comma separated list. The first background listed is on top. And you can manipulate all the backgrounds with a single background property (center them all) or give them all unique values—again with commas. Do not give less unique values then the number of backgrounds you’re layering.

Dividing a section to avoid boxy looking design

This technique isn’t all that different from the first, however we’ll want the blob background to be clipped in a way that the the blob edge is showing on a single boundary (top, bottom, left, or right).

Since we want the blob clipped, we’re going to make the blob’s background-size bigger then 100% with the keyword cover to stretch your blob to fill the entire container or a literal value such as 150%. Then we’ll position it to a side with background-position: 50% 500px. We’ll play around with these values to work with the given blob shape.

    background-image: url('data:image/svg+xml,<svg xmlns="" viewBox="0 0 2000 1155" fill="%23808"><path d="M968.99 1033c-187.45.85-174.59 47.22-449 48-352 1-463-239-463-548 0-273.09 190-474 496-472 277.28 1.81 277.22 60.83 470 57 252-5 217.88-56.12 487-57 305-1 431 290 433 526s-117.08 502.4-432 507c-342 5-322-62-542-61Z"></path></svg>');
    background-repeat: no-repeat;
    background-size: 150%;
    background-position: 50% 400px;



In this example, I purposefully made the bottom div the wrong color, so the technique is visible. Ideally the bottom div would match the blob color (purple in this case) to hide the container shape, making the transition seamless between the blob and neighboring container.

If we used the same blob from the first technique, we have a limited ability to position the blob. We can get it looking good, but it’s not responsive, so a change in the container size may shift the blob out of view.

To combat this, I’d prepare the SVG differently, instead positioning the blob, so it’s clipped in the SVG and position the SVG background based on the clipped side. For example, if the blob is clipped at the bottom, like in the image below, I’d bottom-align the background-image with background-position: bottom center.

Placing blob outside the viewbox

You’ll notice, most of the blob falls outside the viewBox. We are only interested in the top edge of the blob. Take notice of how I positioned the blob to fill the entire width.

Masking an image with a blob

In early web design, images were rectangles. To get rounded corners you had use your image editor, like PhotoShop, before bringing it into your web page. Now we have the lovely CSS property Border-radius to make this a cinch.

We can take this one step further and give images a more organic blob shape, using a mask.

There two ways to go about this, mask and clip-path.

I prefer using the CSS mask because it’s straightforward, as long as you’re ok with using vendor prefixes, because Chromium browsers need -webkit- preceding the mask property. But it’s well worth it because you can aslo control the size and positioning of the mask.

  -webkit-mask: url('<dataURI>') center / contain no-repeat;
          mask: url('<dataURI>') center / contain no-repeat;

If I wanted to do the same thing with clip-path, I could but there’s many more steps:

  • declare an inline SVG
  • wrap the path in clipPath tag
    • give the clipPath an ID
    • give the clipPath tag an attribute of clipPathUnits=”objectBoundingBox”
  • convert the path to use coordinates between 0 and 1
  • reference the ID in the CSS clip-path: url(#YOUR-ID);

Unfortunately with this method you’re out of luck if the image and SVG have different proportions, because it will stretch to fit which is often not ideal.

Using SVG blobs is CSS

We went over three practical ways to SVG blobs in your website design: as a background, shape divider, and a mask. There are probably other things you can do with blobs, but I covered the most common use cases and the CSS to apply them to your design.

Feel free to ask questions on the video version of this post (below).

If you’d like to explore and learn more about SVG and get introduced to all the crazy things you can do, make sure you visit — it’s both in blog and video format.

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