Animated Gradient Background with SVG

Published on 04/02/2022

If you keep a little eye on on the background of this site, you'll notice that the gradient in the background changes colour...

I originally tried to create this affect using a regular background-image gradient, but it turns out you can't animate those the way I wanted to. So, I used an inline SVG to get the affect I was after.


Step One: Inline your SVG

SVG's are interesting - if you put one in a regular <img> tag, they act just like regular images. But if you open up an SVG file in your code editor, you'll see they're actually made up of various tags and properties just like HTML.

The SVG I've used on this site actually has multiple colours and a couple of layers (the gradient, plus the squiggly bits on top) but I've made a simplified one for this demo (below) - feel free to right-click and save it if you don't already have one handy to work with.

A simple three-tone gradient
A simplified gradient with three colour stops

So the first thing to do is make our SVG 'inline'. This basically just means we're going to move the SVG code out of the SVG file, and into our HTML. This site uses Twig, so I created a file page-background.twig, and copied the code from the SVG into it.

Your SVG is now inline!

This will work just the same if you just copy the SVG code right into your HTML. SVG code is long though, so it's a lot neater to use an include (how you do this will depend on the language you're using)


Step Two: Position the SVG

You can add classes to the components of an SVG just like you can with regular HTML elements - this is how we'll be able to add the CSS animation to it.

The first thing to do, if you're making a background, is to position the SVG on the page. Find the <svg> tag near the start of the file, and add a class to it, eg

<svg class="page-background" .... >

Then, go to your stylesheet and add styles, for example the below, which make it fill the entire screen, and stay still when the page is scrolled:

.page-background {
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: -2;
  height: 100%;
  width: 100%;
}

One additional step, depending on the shape of your SVG graphic, is to specify how it should fit/stretch within its frame, or 'viewport' - as your browser window won't always be the same shape as the original SVG.

I don't want my SVG to distort, but I do want it to cover the entire width and height of the screen, and I don't mind if it's cropped off. In the <svg> tag again, I added preserveAspectRatio="xMaxYMax slice" - depending on your own SVG, you might want different settings - check out the options on MDN.


Step Three: Move the SVG styles to CSS

Have a hunt through the SVG code until you find the tag <linearGradient> - inside this tag, you'll see a number of <stop> tags

<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="61.1197" y1="9.0747" x2="501.494" y2="279.3545">
	<stop  offset="0" style="stop-color:#ED1E79;stop-opacity:0.5"/>
	<stop  offset="0.5" style="stop-color:#7AC943;stop-opacity:0.5"/>
	<stop  offset="1" style="stop-color:#3FA9F5;stop-opacity:0.5"/>
</linearGradient>

Notice that each one has an inline style tag - we won't be able to change the colours from our CSS with these here, because inline style elements have higher specificity. So, let's move these pesky inline styles into our CSS.

First, add some classes to each of the <stop> tags. I've added two classes to each of mine - every stop has the class stop, and then they all have their own numbered tag too, eg stop--1, stop--2, etc. The first tag will let us apply some styles to all of the stops at once, whereas the second one lets us adjust each one individually.

	<stop  offset="0" style="stop-color:#ED1E79;stop-opacity:0.5"/>
	<stop  offset="0.5" style="stop-color:#7AC943;stop-opacity:0.5"/>
	<stop  offset="1" style="stop-color:#3FA9F5;stop-opacity:0.5"/>

Next, you'll need to create the relevant css, moving the stop-colour property from each inline style to the appropriate class in your CSS. I also moved the stop-opacity property into my shared .stop class, just in case I want to change it later. Here's my CSS:

.stop {
  stop-opacity: 0.5;
}

.stop--1 {
  stop-color:#ED1E79;
}
.stop--2 {
  stop-color:#7AC943;
}
.stop--3 {
  stop-color:#3FA9F5;
}

All the relevant styles for your SVG are now controlled by your CSS, yay! (Have a go at changing the colours and see what happens)


Step Four: Animation Time!

Nearly there! We now have the full power of CSS at our disposal.

I used the Keyframes property to create my animation, which cycles through all the different colours in my gradient - divide 100 by the number of stops to tell you what % to change the colour at to have each shown equally, or just play around with it.

@keyframes page-background-ripple {
  0% {
    stop-color:#ED1E79;
  }
  33.33% {
    stop-color:#7AC943;
  }
  66.66% {
    stop-color:#3FA9F5;
  }
  /* at 100%, the animation loops back to 0 */
}

You could also have the colours change on hover or click, with a transition to make it smooth - go nuts!

Next, apply this animation to the .stop class. I've also added some of the animation functions that are the same for all the stops here, rather than repeat them.

.stop {
  stop-opacity: 0.3;
  animation-name: page-background-ripple;
  animation-duration: 4s;
  animation-direction: alternate;
  animation-iteration-count: infinite;
}

Right now, the gradient isn't going to look much like a gradient at all, because all the sections are cycling though the same colour at the same time. This is where the animation-delay property helps us out - we'll add a different delay to each of the stops.

You could use the shorthand version of the animation property in the .stop class to set all the sub-properties at once, provided you use the separate animation-delay property on the individual stops. I've just broken it up for clarity here.

In my code below, I'm actually using scss, not just regular css, so I've made use of variables (the lines starting with a $ sign) to help me work out the timings. If you're just using regular CSS, you'll just have to swap out the places where the variable is used (eg $base-delay * 2) for the actual number of seconds to delay for - and remember to update these if you change the total duration of your animation.

If my animation is 15s long, with 3 frames, I want to delay the animation on the second stop by 1/3 of 15s (5s) and on the third stop by 2/3 (10s).

$duration: 15s;
$base-delay: $duration / 3;

.stop--1 {
  stop-color:#ED1E79;
  animation-delay: 0s;
}
.stop--2 {
  stop-color:#7AC943;
  animation-delay: $base-delay;
}
.stop--3 {
  stop-color:#3FA9F5;
  animation-delay: $base-delay * 2;
}

Each stop now starts its animation a little after the previous one, so they're never at the same point in the animation at the same time, and our gradient is restored!

A simple three-tone gradient
Appearance at 0s - only the first stop is animating
A simple three-tone gradient
Appearance at 5s - the second stop begins animating
A simple three-tone gradient
Appearance at 10s - the third stop begins animating

And we're done!

You can use your browser's dev-tools to edit the styles on an SVG just like on other elements - useful to identify which path is which! (You can then give them a descriptive class name to make life easier)

That's it - you've now got an animated gradient, in the form of an SVG. Mess about with different colours, durations, delays, and timings, until you find something you like.

You can also use this technique to animate an SVG that isn't a gradient, by changing the styles on a <path> element instead - have a poke around at what elements make up the SVG, and what properties you can set on them.