Dynamically chain CSS animations
In our previous article we learned a technique to animate HTML elements dynamically with JavaScript using CSS transitions. Now we go one step further and design a chain of animations.
When we use transition
we specify several states that gets interpolated by the browser.
Our next challenge is to move a box clockwise: right, down, left and up.
An alternative: This problem can also be solved with
@keyframes
which has some implications. I solved it in this article. For now we want to stick with JavaScript and CSS transitions to solve this problem dynamically.
The basics
First we define an HTML element with a CSS class box
:
<div class="box"></div>
It has the following CSS stylesheet:
.box {
width: 100px;
height: 100px;
background-color: #9D5B75;
transition: transform linear 500ms;
}
Within JavaScript we fetch the element using a CSS selector:
const box = document.body.querySelector("div.box")
We already know that we can animate it with requestAnimationFrame
into a second state. What we lack is some kind of “getting informed when the animation to the new state is finished”. Therefor we can listen to an event called transitionend
:
box.addEventListener("transitionend", () => console.log('finished'))
For this example I prefer tinting the color of box into red when the animation completes. Have a look:
Chaining states together
Now the idea is to utilize the transitionend
event to start a subsequent animation by doing a state change.
Be careful / trap: The first thought might be to put a subsequent state change into the callback function of the
transitionend
event. This gets confusing really quickly. Don’t do that.
To solve this — without doing a mess — we define a list/array with functions. Every function describes how to get to a specific state.
const states = [
() => box.style.transform = "translate(0px, 0px)",
() => box.style.transform = "translate(100px, 0px)",
() => box.style.transform = "translate(100px, 100px)",
() => box.style.transform = "translate(0px, 100px)"
]
We also introduce a variable that keeps track of the current state:
let currentState = 0
A value of 0
means that we are in a state that relates to states[0]
.
Then we need a function, we can call, whenever we want to jump to the next state:
const animateToNextState = () => {
requestAnimationFrame(() => {
states[++currentState % states.length]()
})
}
The expression ++currentState % states.length
results in the index of the next state.
What is going on: If we put
currentState = 0
into that expression, it increases by one to1
.states[1]
is backed by a function that executes the “move right”. This is exactly what we want to do first.The modulo operator
%
makes our computed index repeating right after we reached the last index ofstates
. AcurrentState
of3
increases by one results in4
. By applying the modulo now, it gets effectively0
again.
Finally we tell the browser to call our animateToNextState
function after any CSS transition completes…
box.addEventListener("transitionend", () => animateToNextState())
… and poke the animation chain once:
animateToNextState()
The result
Now let’s try our implementation…
Challenge completed.
Let’s do something more. In my next article we will focus on creating a pure 100% JavaScript based animation without using CSS Animation or Transition that will run super smooth.