Building Gradient Animations with CSS Key Frames, TailwindCSS, and ReactJS
January 1, 2023
Recently I worked on a project, one of the requirements was a subtle gradient animation in the background of the site - kind of like the ones we’ve seen on sites like Stripe’s. I’ve always wondered how you these animations get crafted and pulled off because they can have a big impact on the tone of a site.
Only take what you need on the way
If you're performance oriented with the way you build for the web, then you should be conscious about which external dependencies you pull into a project. Most of the time, new dependencies mean a bigger bundle, more JavaScript, slower load times. From my experience, pulling in lots of extra dependencies that you don't need is a recipe for danger whether you've got a big team of developers or a small one. Those external dependencies will have dependencies of their own and at some point down the line, one way or another something stops getting maintained or gets incompatible with something else. That's why I'd suggest only pulling npm
packages into your project if you really need them and can't avoid it.
Almost a year ago, I participated in a hackathon with a friend and we built (DigitalFootprint.earth)[https://digitalfootprint.earth] to look deeper at the carbon footprint of sites on the web. With billions of users on the web, visiting billions of sites, the carbon footprint of our internet is very much invisible to most of the world. People buy electric cars, travel less, eat locally sourced food but the time they spend on the internet also has a carbon impact. I bring this up because a big part of the carbon footprint of your site is determined by the amount of data that is transferred. More packages, means more javascript and more data passed around - which means taking the time to focus on performance and optimise your builds is much more environmentally and socially responsible too.
But you didn't come here to listen to me rant about performance and bundle sizes so let's build some gradient animations!
Implementation
We’re going to build this animation in the context of a React project with TailwindCSS and some plain old CSS Keyframes. People always get excited to jump to the next amazing animation framework, but you can do a lot with some simple keyframes.
The way the code has been structured, I’ve tried tried to keep it flexible and basic as possible to make sure its maintainable and can easily be extended. There are quite a few benefits to keeping things simple this way.
- For simple animations using CSS keyframes is easy and doesn’t require any complicated JavaScript knowledge, just a bit of imagination!
- CSS native animations are usually very performant and have great cross browser and device compatibility. Other script generated animations are often far less compatible with other browsers and devices but CSS is pretty universal! Fair warning, even in 2022 you should still check what CSS transforms you’re using because they’re not all compatible with every browser if that sort of thing matters to you.
- CSS native animations also give the browser back some control (which you may or may not want)! If the browser can control your animation sequences then it can optimise them for your site, but if they’re central to your experience maybe you’d be more reluctant to relinquish that control.
How do key frame animations work?
If you’re already familiar with keyframe animations feel free to skip ahead, but if you’re not here’s the 101 intro.
Keyframes are specified in a CSS file and prefixed with the keyword @keyframes
that acts like a decorator to name the class for the animation. The name “key frame” comes from films, where each frame represented a point in time (a “frame”) in a timeline or transition. When these key frames happen very quickly in succession, they emulate movement between the frames.
In CSS, we start with the @keyframes
decorator and then specify the keyword for the animation. Inside, we can specify discrete steps that define the different steps in the animation. In the simplest case, we can specify where the animation should start and stop with the keywords from
and to
:”
/* Keyframe animation that rotoates something in a full circle */
@keyframes full-rotate {
from {
transform: rotate(-360deg);
}
to {
transform: rotate(360deg);
}
}
How to apply keyframe animations
CSS Keyframes are applied using the CSS animation property. The animation property has many sub properties to specify exactly how the animations should work. One of the best places to learn more about keyframes and animations is in the Mozilla docs - they explain it so well there’s no point for me to repeat it here.
/* CSS classname we attach our animation to */
.animate-this {
/* Define the animation by its name, timing function, how often its repeated, and duration in seconds */
animation: full-rotate linear infinite 10s;
}
Enough with the basics
The only real difference between our more elegant keyframe animation and the one in the example above is that instead of just using from
and to
, we’re going to use percentages to define each more of the steps in our animation. I would highly encourage you to play around with these if you can to tune this animation to your heart’s content.
The key is that the ends have to match up if you want infinite animations to /feel infinite/ without a glitch or a noticeable finish. Otherwise it’ll look like a poorly timed TikTok trying to make it seem like an infinite loop but the timing is juuust a little off.
In this case, we’re dealing with rotations and translations (movements in the vertical and horizontal axis), so this means we want to make sure that regardless of how we change the numbers, the initial (0%) and final (100%) translations and degrees line up. With percents for the translations, 0 = 0 (seems obvious enough), but just to throw a bit of maths in here, with degrees you have to remember that 0 matches up with 360 and every multiple after that. Any value you want to use in degrees should line up with itself +/- 360deg.
@keyframes move-around {
0% {
transform: translateY(-0%) translateX(-0%) rotate(0deg) translateX(-15%);
}
25% {
transform: translateY(-10%) translateX(-10%) skew(5deg, 5deg) rotate(95deg)
translateX(-5%);
}
50% {
transform: translateY(-10%) translateX(-10%) rotate(170deg) translateX(-15%);
}
75% {
transform: translateY(-10%) translateX(-10%) skew(-5deg, -5deg) rotate(
275deg
)
translateX(-10%);
}
100% {
transform: translateY(-0%) translateX(-0%) rotate(360deg) translateX(-15%);
}
}
With that we have our keyframe animation code but we still have to attach it to an element in our project. The snippet I’ve included below is written in React and TypeScript and uses TailwindCSS to make the code easier to read. There’s now requirement to use TailwindCSS, TypeScript, or event React - you can do everything we’ve just done using whatever tools you like and you have to pick what’s right for your (or your teams) experience and stack.
import * as React from 'react'
interface IPropTypes {
children: React.ReactNode;
}
const AuroraBackground = ({ children }: IPropTypes) => {
return (
<div className="relative flex h-full w-full flex-row items-center justify-center overflow-visible">
<div
className="absolute left-[40%] right-[60%] top-[30%] z-0 h-[300px] w-[300px] rounded-full bg-emerald-500 opacity-80 blur-[200px]"
style={{
animation: 'movement linear infinite 8s',
}}
/>
<div
className="absolute left-[60%] right-[40%] top-[30%] z-0 h-[400px] w-[400px] rounded-full bg-indigo-600 opacity-50 blur-[200px]"
style={{
animation: 'movement linear infinite 12s',
}}
/>
<div className="z-40">{children}</div>
</div>
)
}
export default AuroraBackground
Closing up
I use this animation in the context of a NextJS project with React, Typescript and TailwindCSS for my personal website and also for some other bigger projects currently in the works but there’s no reason you have to do the same. Everything here should be easily transferrable and if you do try it out I’d love it if you could share this article and say hi on Twitter to share what you’ve come up with!