Dynamically chained CSS Transitions

Felix Blaschke
3 min readApr 30, 2018

--

This article is a part of my Web Animations article series.

For now we looked at static animation techniques using CSS Animation and CSS Transition. Let’s move a step towards to the arts of dynamic animations.

The first technique we will discuss is dynamically chained CSS Transitions. Therefor we combine JavaScript-based animation control with the animation execution of CSS Transition. The result is a flexible but still a lightweight solution.

The basic pattern

The basic pattern of a chained CSS Transition consists of 4 steps:

  1. Configure CSS Transition
  2. Define the animation states
  3. Create an animation controller
  4. Wire events into the animation controller

1. Configure CSS Transition

First of all we enable our element to use CSS Transition for properties we want to animate:

transition: transform 500ms linear;

We can use default values here as we are free to modify them dynamically later.

2. Define the animation states

The animation states are quite similar to keyframes in CSS Animation. The main difference is that you express these states in code.

In our example we move a box 100px rightwards:

const box = document.body.querySelector(".box")

const states = [
() => box.style.transform = "translateX(0px)",
() => box.style.transform = "translateX(100px)"
]

Each state is a function call in which we have the full power of JavaScript.

3. Create an animation controller

An animation controller is some piece of code that will control our animation. It serves as our single source of truth for our animation process.

Here is a very simple pattern for an animation controller:

let currentState = 0

const animateNextState = () => {
const nextState = states[++currentState % states.length]
nextState()
}

Everytime we call animateNextState() it increases the counter variable currentState by one. The modulo operator % helps us with the repetition of the animation.

4. Wire events into the animation controller

Finally we wire all events into the animation controller. As we are using CSS Transition we configure the transitionend event to trigger the controller:

box.addEventListener("transitionend", () => {
requestAnimationFrame(() => animateNextState())
})

The requestAnimationFrame function is a browser function that enqueues our code into the rendering pipeline of the browser.

Now we need to decide when to start our animation. You can just call the following code where you like to:

requestAnimationFrame(() => animateNextState())

We call this just outside an EventListener so this animation will start immediately.

Now we can take a look at our animation:

Add some magic

Until here we implemented something similar to CSS Animation. So let us take advantage from being dynamic. The animation controller can have a configuration object to expose the animation’s configuration parameters:

const configuration = {
movement: 100,
duration: 500,
}

Now we can apply the configuration parameters either inside the controller function or withing the state function.

Controller function

Our configuration parameter duration can be implemented here:

const animateNextState = () => {
box.style.transitionDuration = configuration.duration+"ms"

const nextState = states[++currentState % states.length]
nextState(configuration)
}

This will override our default CSS Transition. Note that we also pass the configuration into the nextState function.

State Function

The state function receives the configuration as a function parameter. We can now apply our movement parameter to our transform statement:

const states = [
(conf) => box.style.transform = "translateX(0px)",
(conf) => box.style.transform = "translateX("+conf.movement+"px)"
]

Any code can now modify our animation configuration and the parameters visually apply after each transition.

Here is an example:

Some examples

More states

You can easily add more animation states to our states list. This animation contains of 4 states modelling a circular movement:

Single dynamic state

You can also do the opposite: just one state. But in this state we compute random values for our box position:

const states = [
(conf) => {
x = Math.random() * conf.movement
y = Math.random() * conf.movement
box.style.transform = "translate("+x+"px, "+y+"px)"
}
]

--

--

No responses yet