Floating, Pulsing, and Spinning CSS Animations
Breaking down three CSS animations, with a dash of JavaScript.
This TinyApp is The Magic Potion. It features an image of a potion that floats, pulses, spins and rests. The focus of this TinyApp is CSS, but there's a bit of basic JavaScript mixed in, too. I'm not using any fancy frameworks in this TinyApp, just plain ol' HTML, CSS, and JavaScript. Let's get to it.
Meet The Magic Potion
The Magic Potion has an image of a potion and some buttons to give the potion some infinitely looping CSS animations. These kinds of animations can give some life to your application, and add an extra bit of delight. While there are certainly libraries that add CSS animations for you, it's good to know what's happening under the hood. You might want to write your animations yourself!
index.html ๐
The first file to take a look at is our index.html
- it has all the elements that we're rendering on this web page. Notably, there's the image of the potion, and a set of span
's that have ids that have the word "button" written on them.
<body>
<div class="container">
<h1>The Magic Potion</h1>
<img id="potion" class="resting" src="./potion.svg" alt="potion" />
<div class="animations-container">
<span class="active" id="resting-button">Resting</span>
<span id="pulsing-button">Pulsing</span>
<span id="floating-button">Floating</span>
<span id="spinning-button">Spinning</span>
</div>
</div>
<script src="src/index.js"></script>
</body>
Without knowing what the CSS and JavaScript in the rest of this project does, we can infer from this file that when one of the span
's are clicked, the animation applied to the potion image is changed.
style.css ๐จ
The style.css
file has the basic layout and styling of the app. I'll breeze over this, because we're mostly here for the animations. Some CSS classes I'll point out specifically are:
.active
.active {
color: #9ecbe0;
}
This class is just applying a color to some text, but we'll be using it in our JavaScript later.
img
#potion {
width: 12em;
margin: auto;
display: block;
}
This styling will be applied to the potion image (since it has an id of potion). A tidbit here is that I set the width to be a static value: 12em
. This is important because some of the following animations won't work on elements that have "relative" width values, like percentages.
animations.css ๐งช
Okay, it's time to rumble! This file includes the CSS animations and the classes that make them happen.
These animations are "keyframes" animations, which means that we're using CSS to tell HTML elements what to do with themselves during certain points in the animation cycle.
Floating
The floating animation moves our potion bottle up and down infinitely. What we're doing in reality, is telling the potion image to move up on the y-axis of the page, and come back down. We create a keyframe animation with those commands like so:
@keyframes float {
0% {
transform: translateY(0%);
}
50% {
transform: translateY(15%);
}
100% {
transform: translateY(0%);
}
}
At 0%, AKA when the animation first starts, we want the potion to stay grounded, translated 0% from its original position.
At 50%, we're halfway through the cycle, so we want to move 15% of its height along the y-axis. What's interesting is that the (0,0) or the origin coordinate of a web page is actually in the top-left hand corner of our page. So when we translateY(15%)
, we're actually moving down 15% of the element's height.
At 100%, we return back to our base level, having our translateY
at 0%.
Now, to actually apply our animation, we need to create a CSS class to animate it.
.floating {
animation: float 2s infinite;
}
The floating class applies an animation of float
(our keyframe that we defined above), and wants the cycle of that animation to be 2 seconds long. It's also meant to be repeating infinitely.
NOTE: This animation
property is actually shorthand for lots of different animation properties. I find it particularly readable so I'm using the shorthand version here. Check out the "further reading" at the bottom of this page if you want to dig into more details.
That's how floating works! I'll break down the JavaScript to apply this animation after we understand pulsing and spinning.
Pulsing
Our pulsing animation makes our potion looks like it's moving forward and back (as if it's 3D). When done well, this subtle animation can look great in projects.
Our super simple pulsing animation creates this effect by scaling the size of the potion, infinitely. Our keyframes describe it like this:
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(0.9);
}
100% {
transform: scale(1);
}
}
At the beginning of the animation cycle, 0%, we want the scale of our element to be its regular size, 1.
At 50%, we shrink it down to 90% of its original size, or 0.9.
At 100%, we bring it back to full size, 1.
Between float and pulse, we've seen transform being used quite a bit. Transform has a lot of interesting abilities to mutate our HTML elements. It's especially handy in these CSS animations, but can be used in all sorts of contexts.
To use this keyframe animation on an actual CSS class, we create this .pulsing
class:
.pulsing {
animation: pulse 3s linear infinite;
}
Here, animation
is saying: anything with the .pulsing
class will have the keyframe animation pulse
, the duration of the animation will be 3s, it will move linearly, and repeat infinitely.
This is the first time we're seeing this "linear" thing in our animation. "Linear" describes the animation-timing-function. Basically, this property sets the way that an animation moves through each cycle.
This timing function is where animations can get quite advanced and intricate, so for the purposes of this TinyApp, I've kept it simple and only included "linear" timing functions. There's so much more to explore here, so I included the docs in the further reading section.
Spinning
Our final animation is spinning! To spin our potion around, we want to infinitely rotate it. That brings us to our keyframes:
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
At 0%, the starting point, we start at 0deg
AKA 0 degrees. By 100%, we want the potion to rotate 360 degrees, effectively putting the potion in the same place where it started.
Our CSS class will be .spinning
and similarly it tells the animation to run linearly and happening an infinite number of times.
.spinning {
animation: spin 10s linear infinite;
}
index.js ๐
Now that we have our animations ready, let's apply them when the buttons are clicked on. First, we'll want to grab our potion:
const potion = document.querySelector("#potion");
The querySelector
method is getting a reference to our potion using its id. Now we can manipulate our potion in JavaScript by referring to the potion
variable.
We do the same for each of the buttons:
const restingButton = document.querySelector("#resting-button");
const floatingButton = document.querySelector("#floating-button");
const pulsingButton = document.querySelector("#pulsing-button");
const spinningButton = document.querySelector("#spinning-button");
When we click a button, what should happen? Let's say we wanted to click on our "floating" button. The following would need to happen:
- Apply the "floating" class to our
potion
, so that it starts to float. - Apply the "active" class to the
floatingButton
so that it shows the user that it's the active button. - Make sure that no other buttons have the "active" class on them.
There are many ways to do this. JavaScript objects or arrays could easily be used. I opted for a more verbose, less clever, but more readable option, to illustrate the JavaScript super clearly.
Here's the code for what happens when the floatingButton
is clicked:
floatingButton.addEventListener("click", (e) => {
potion.className = "floating";
floatingButton.className = "active";
pulsingButton.className = "";
restingButton.className = "";
spinningButton.classList = "";
});
Here's how it reads, in English: Let's listen for a "click" event on floatingButton
. When it's clicked, run a function that:
- Changes the potion's class to "floating" (giving it the floating css animation!)
- Changes the floatingButton's class to "active" so that its text color changes and we know it's active.
- Changes pulsingButton, restingButton, and spinningButton's class to nothing.
This is done for each button. While it's certainly not the most clever way to handle this, it's the most explicit and illustrative, so I'm leaving it. ๐
And that's the whole TinyApp! The Magic Potion isn't so magical now, is it?
This is just the beginning of your CSS animation journey. There's lots to learn, but I hope this TinyApp helps demystify things you may not have known before. As always, let me know your feedback, questions, and comments!
Happy Animating! ~ Saalik