Sequencing animations in ReactJS

Sourabh Parmar
Prod.IO
Published in
5 min readJan 4, 2018

Animations are very important to create a great user experience. As developers, we deal with complex animations to make the user experience even better. In this post, I won’t be talking about the animation, rather I will be focusing on how we can sequentialize complex entities, including animations, UI elements etc.

Recently, while working on a web app, built on ReactJS, I came across this problem of sequentializing multiple animations. There was a scenario, where I needed to animate multiple components, in a particular sequence. For example, one component had to finish before another starts animating. The problem was growing with the requirements.

I googled the problem and found some solutions, but as my application started to grow, it became very difficult to solve all the problems with the help of a single solution. Finally, I decided to write a solution on my own and tried to abstract it, so that most of the problems can be generalised and solved easily.

The Solution

Let’s say we have three components in our app “A, B and C”, and A needs to be animated first followed by B followed by C.

                           A => B => C

If the current animating component can notify the next component to be animated, the problem is solved.

To notify other components in ReactJS, either we can give responsibility to the component itself, or to the common parent, or to the topmost level, i.e redux.

Direct communication between components is considered as bad practice in ReactJS. There are few limitations in putting the logic at the parent level, for example, if the components need to be animated do not share the same parent, or maybe they are too far from each other.

So it’s better to put the logic in the Redux. All our components listen to store and can be notified when to animate.

In Action

Let’s start by creating a simple reducer which will have a queue of components need to be animated.

state = {
animationQueue: [A, B, C]
}

Should animationQueue be a normal queue or priority queue?

There is a possibility that components need to be animated based on priority. A high priority animation cannot wait in the queue and needs to be animated as soon as possible. Based on the use case, animationQueue can be a normal queue or a priority queue. In this post, we will assume animationQueue to be priority queue so that we can cover as many use cases as possible.

All the components will have a priority associated with them, and as soon as there is an entry in the queue, it will shuffle and will be arranged based on the priority.

state = {
animationQueue: [
{
A,
priority: 1
},
{
B,
priority: 2
},
{
C,
priority: 3
}
]
}

What’s next? We need another variable to store the current animation component.

state = {
animationQueue: [
{
B,
priority: 2
},
{
C,
priority: 3
}
],
currentAnimation: 'A'
}

What about currentAnimation, can we think of a scenario where we need to animate multiple components together?

Yes, right? And we know the solution as well, our currentAnimation can’t be a string anymore, it needs to be an array.

state = {
animationQueue: [
{
B,
priority: 2
},
{
C,
priority: 3
}
],
currentAnimation: [A]
}

Now the reducer is ready and is responsible for managing the animation queue and the current animation.

All the components are subscribed to the store. The components in currentAnimation array start animating. Once a component is finished animating, it notifies the reducer, which pops out that components from the currentAnimation array. Once the currentAnimation array is empty, the next set of components are dequeued from the animationQueue and pushed to the currentAnimation array in order to animate.

While dequeuing from animationQueue, we need to make sure that we dequeue all the same priority components and push them into the currentAnimation array.

In this way, we can ensure that all the components are animated in the particular order only.

Only Animations??

Is the solution restricted to only sequencing the animations? The answer is no.

The solution is generic, and we can push anything to our queue. For example, if we want to render a component once “A” finishes animating, and on some user interaction we want to remove the component from the DOM and starts animating “B”. It’s simple; assign the correct priority to each entity and listen to the reducer. Whenever your turn comes, do your job, and once the job is done, inform the reducer, it will take care of the remaining things.

So all the major things, be it animation, rendering a component, opening and closing of popups or anything actionable, which should be part of any sequence, can be solved using this approach.

Multiple Sequences

Till now we discussed a single sequence consisting of one or more elements. But we can have a scenario where a sequence of animations needs to be followed by another sequence of animations.

We can create a single sequence by merging the two sequences, but that’s not the ideal solution because it’s not scalable. And also the individual sequences may have there own significance, and should not be merge.

The solution is to have one more queue, the sequenceQueue, either a priority or normal, depending on the use case. The sequenceQueue is responsible for managing the multiple sequences and acts as a source of input for the animationQueue.

state = {
sequenceQueue: [
{
sequence: [
{
X,
priority: 1
},
{
Y,
priority: 2
},
{
Z,
priority: 3
}
],
priority: 2
}
],
animationQueue: [
{
B,
priority: 1
},
{
C,
priority: 2
}
],
currentAnimation: [A]
}

The sequenceQueue dequeues a sequence only once the animationQueue is empty. It makes sure that a particular sequence is always executed in the same manner, and is uninterrupted by maybe even a high priority animation in another sequence.

So now we can handle multiple sequences without solution, and it’s scalable as well.

Conclusion

The generic nature of solution gives us the flexibility to modify it as per our requirement. I used this solution in my ReactJS app and discussed in that context only. However, if we look at the solution closely, we are using basic data structures and javascript events. So this solution is not restricted to ReactJS, but with slight modifications, it can be applied to any javascript framework.

The problem of sequencing complex entities is solved quite easily by using basic javascript concepts. The structured nature of the solution makes it very easy to implement and maintain.

--

--