In this article, I will demonstrate how to animate gradients in an infinite, linear fashion using nothing but HTML and CSS. This is not the only animation technique for gradients, but I've found it's one of the more elegant and versatile.
Part 1: Understanding the Technique
This animation technique uses several elements to achieve this effect.
First, let's establish those elements and their vocabulary in a scene:
- Stage - A parent container that designates where the visible area of the animation begins and ends. Think of this like a theatre stage, where the left and right sides are blocked by curtains. Anything that goes from the visible area of the stage to behind the curtains will be invisible to the audience. For now, everything is visible for demonstration purposes.
- Belt - A child container nested within the stage to house visually-distinguishable elements. Think of this like a portion of a conveyor belt at the grocery store. Items on top of it are stationary but the belt moves items through space. This is what actually performs the animation.
- Aspects - A sequence of visually-distinguishable elements within the belt. This example uses dots. Later, we'll use gradients. Whatever elements you choose, there are several rules:
Rules of Aspects
- There must be at least two aspects in the sequence.
- The first and last aspect(s) must be identical.
- All aspects must be evenly spaced along the belt.
- Note: even spacing is achieved here with Flexbox's
justify-content: space-around
.
- Note: even spacing is achieved here with Flexbox's
In this example, the intention is to animate the aspects from the left to the right of the stage, so we start with the position of the belt offset to the left of the stage. The width and the offset distance of the belt are significant. They determine how many aspects will appear within the visible area at any given moment of the animation.
This animation has two aspects where one aspect will be visible at any given time. That means the last aspect of the sequence (Aspect #2) must visually appear in the exact same position on the screen in which the first aspect of the sequence (Aspect #1) will appear at the end of a single iteration of the animation.
Basic Idea
Start with the right side of the belt at the right-most edge of the stage. Animate the belt until its left side reaches the left-most edge of the stage.
When the belt completes a single iteration of its animation, it instantly teleports back to its original location, where the last aspect of the sequence (Aspect #2) visually replaces the first aspect of the sequence (Aspect #1) and continues its journey on the next iteration of the animation.
This is what that looks like:
Then, just apply an overflow: hidden
to the stage and its sides begin to behave correctly as theatre curtains to complete the illusion of perpetual motion within the visible area:
As you can see, one of two aspects is fully visible at any given time.
Bonus Example
Here's what that same setup looks like with three aspects:
After the overflow: hidden
is applied, two of the three aspects are fully visible at any given time.
Note that the second and third aspects at the beginning of the animation appear identical to the first and second aspects at the end of the animation.
Part 2: Animating Gradients
Let's begin by establishing the first fundamental HTML element: the stage. We're using an article
tag here, but any HTML element can be substituted.
<article class="stage"></article>
Give it some arbitrary dimensions in CSS to designate the visible area:
.stage {
height: 200px;
width: 200px;
}
Add some optional presentational styles:
body {
background-color: #8fb6ff;
margin: 0;
padding: 3rem;
}
.stage {
background-color: #fff;
box-shadow: 0 3px 14px #000;
border-radius: 1rem;
height: 200px;
width: 200px;
}
Since we're using gradients as the aspects, we can take advantage of CSS3 and utilize background-image: linear-gradient()
to make the stage's background act as both the belt and aspects and avoid superfluous HTML. The stage's background-image
will establish the belt and the linear-gradient()
will define aspects.
Adding Gradient Colors
For the stage's gradient, we'll use the colors Cerise and Mustard.
background-image: linear-gradient(90deg, #da3287, #ffde5e);
A convenient thing about linear-gradient
is that browser technology has advanced so much that browser vendor prefixes for this CSS function are no longer necessary for this to work in all contemporary browsers.
In this animation technique, a gradient is an aspect. It's easier to abstract gradients as aspects when you think of each pair of colors in a gradient as a single element.
At the moment there's only one aspect, a gradient of Cerise to Mustard, which violates the rules of aspects because there needs to be at least two aspects in a sequence.
Let's duplicate this color pair to create two more aspects.
background-image: linear-gradient(90deg, #da3287, #ffde5e, #da3287, #ffde5e);
Now there's three aspects:
- A gradient of Cerise to Mustard
#da3287, #ffde5e
- A gradient of Mustard to Cerise
#ffde5e, #da3287
- A gradient of Cerise to Mustard
#da3287, #ffde5e
Setting the Width of the Belt
Right now all the aspects are visible on the stage. A change needs to be made to ensure that only one aspect will be visible at a time during the animation.
This particular animation will animate on the X-axis which means the width of the stage's belt needs to be modified. This is achieved by multiplying the X-axis of the background-size
(100%) by the number of aspects (3).
background-size: 300% 100%;
The Y-axis is set to 100% because we want the gradient to cover the entirety of the stage's height.
Although it looks as it did before, the background of the stage (the belt) has actually been extended three times beyond the bounds of its parent container in the horizontal direction. This isn't visible because the stage is doing its job and limiting the visible area.
With the belt and aspects all ready to go, all that remains is animating.
Animating the Belt
The background-position
of the stage's background-image
(its belt) is set by the browser to 0 0
by default. This means the gradient is positioned at the top left of the stage.
We want the position of the background to animate from its left-most edge (where it is currently) to its right-most edge.
These @keyframes
describe that animation behavior:
@keyframes animateBg {
0% {
background-position: 0 0;
}
100% {
background-position: 100% 0;
}
}
Lastly, give this behavior to the stage.
.stage {
animation: animateBg 2s infinite linear;
...
And voila, a linearly-animated gradient that moves infinitely to the left.
Tools
CSS Gradient Animator
This is the classic tool from Ian Forrest that uses a slightly different technique to achieve an eased, oscillating animated effect.
Pure CSS Animated Gradient Generator
I recently wrote a tool in React to generate the same CSS we just used to create infinitely linear animated gradients. It allows you to customize colors, duration, and the direction.
I've also open sourced this tool on GitHub and GitLab for anyone who wants to contribute features or improvements:
Playing around with these tools should give you a deeper understanding of the mechanics behind animating gradients.
Critique and suggestions are welcome.
Thank you for reading. đź‘Ť
Top comments (4)
Hey. I found an other solution but if involves to add an absolute element.
linear gradient from SVG can be animated so you can change the color and the stop color offset. all properties can be animated.
Sure, but at the very least you still need CSS to animate the SVG. My technique bypasses the need for SVG.
This is a very cool technique, Grant! Thanks for laying it out step by step. Unfortunately, I can't use it when there's heavy JavaScript blocking (like doing heavy rendering of DOM elements) because this animation freezes, even with will-change: background-position added to the CSS rule. Looks like background-position isn't one of those directives (like transform and opacity) which get offloaded to the GPU and run in their own thread. Going to keep looking for a gradient bar spinner that won't freeze when the browser main thread is occupado.
Interesting. I'm curious what combinations of browser, operating system, and GPU produce the behavior you've described.