Improve React.js apps with Motion Design

Flat design has been on the rise for several years to the point where most design web frameworks have adopted flat design and all look similar. User experience has taken a lot of importance in apps and many companies will prefer to focus their design effort on user experience than creating a UI from scratch.

Motion design is one part of UX that is getting a lot of attention these days, especially on mobile. It is a great way to guide users through interactions with the application. But it is still underused in web apps. Material Design incorporates some motion design (feedback animation on button click for example) but Bootstrap is still pretty much without any motion.

There are plenty of animation libraries manipulating the DOM at its convenience. But Ember.js, Angular.js and React.js don’t play nice with external libraries manipulating the DOM and these libraries may not work properly with these frameworks.

In this article, I am going to use the official components from React.js addons. There are other components/libraries that are better for the job but at the end of the article you will get a good amount of React.js animation practice to better understand these other components.

Motion Design as a UX technique

Motion Design is not just to make apps look cool. I said it earlier animations should be functional. They guide users through their actions so they don’t get lost.

The example for this article will be a list where one can remove and add items. Deleting an item is a serious action and should be highlighted properly. Making the item disappear in the blink of an eye can be disconcerting, especially if the items look very similar so you have to think a little bit to be sure you have deleted the right one. Here’s the list without any animation:

You can check out the HTML and the CSS in the github repo. The React.js code is below:

Using CSSTransitionGroup

Adding basic animations are very simple and the next step will not be different from the React.js documentation about animations. First we install react-addons-css-transition-group:

npm install --save react-addons-css-transition-group

Then we import it below the React.js import declaration:

import React, {Component} from 'react'
import {render} from 'react-dom'
import CSSTransitionGroup from 'react-addons-css-transition-group'

And finally we use CSSTransitionGroup component wrapping our list items:

Transitions have 2 default states: enter when an item is added and leave when an item is removed. CSSTransitionGroup will add list__item — enter and list__item — enter-active CSS classes to new items, and list__item — leave and list__item — leave-active CSS classes to items being removed.

CSSTransitionGroup adds -enter, -leave, -leave-active, -enter-active to transitionName. I chose here to use the BEM notation that’s why my transitionName had an hyphen at the end.

Setting the timeout for leave makes CSSTransitionGroup wait until the animation is finished before removing the item from the list. The timeout for enter is to remove the transition class names once the transition is finished. Our CSS for these transitions look like this:

.list__item--enter {
animation-name: add-item;
animation-duration: 500ms;
}
.list__item--leave {
background: #bf4a3c;
animation-name: remove-item;
animation-duration: 520ms;
}

We don’t have to use -active classes because we use CSS animations instead CSS transitions. If you want an example of transitions, refer to the documentation. add-item and remove-item are two animations I created in CSS:

@keyframes add-item {
from { transform: scale(0, 0); }
75% { transform: scale(1.1, 1.1); }
to { transform: scale(1, 1); }
}
@keyframes remove-item {
from { transform: scale(1, 1); }
25% { transform: scale(1.1, 1.1); }
to { transform: scale(0, 0); }
}

Now our actions on this list make a lot more sense:

But these animations are not perfect. When an item is removed, the items below it don’t have animations and just take the place of the removed item in an instant. It would feel more natural to get these items to move gradually. That’s where it is going to be more difficult.

Going low-level with TransitionGroup

react-addons-css-transition-group is based on react-addons-transition-group which is a low-level for transitions when one want to do complex transitions.

npm install --save react-addons-transition-group

The import is also similar:

import React, {Component} from 'react'
import {render} from 'react-dom'
import TransitionGroup from 'react-addons-transition-group'

Let’s first re-implement animations for adding and removing items before adding animations to move items gradually after a deletion. Let’s start by replacing CSSTransitionGroup by TransitionGroup in List component:

We also set the duration of both animations in ListItem instead of CSSTransitionGroup. TransitionGroup is adding a few events to children. We will use componentWillEnter(callback) to add list__item-enter CSS class and componentWillLeave(callback) for list__item — leave.

We are done re-implementing our 2 animations. For each animation we set a state to add the related CSS class and we have a timeout running the callback to tell to TransitionGroup that our animation is done. The timeout for componentWillEnter is also setting the state back to its original value (you don’t need to do it for componentWillLeave because it is removed so we don’t care about its state after the animation is done).

We now want to move items below the removed one after the the leave animation is done. In order to do that we are going to set the moving items in the state of List when an item is removed and pass a isMoving prop to ListItem if it’s a moving item. When the animation is done we will reset the state.

We are also adding a moveDuration prop so we can reset moving items when both leave and move animations are done. In ListItem we are going to add list__item — move CSS class to items with the isMoving prop set to true. Here’s the code for the CSS class:

.list__item--move {
position: relative;
animation-name: move-item;
animation-delay: 500ms;
animation-duration: 220ms;
}

We also need to hide the removed item after leave animation is done because the whole animation isn’t done and if we don’t hide it it will reappear.

Here’s the result of the animation:

As you can see basic animations are pretty easy but more complex animations need a lot more code. The animation is still not perfect because moving items should move more independently (the first item begins moving then the second item will move after a slight delay, etc). However it requires even more code and CSS animation properties have to move to the JS code. I have written the code and it is available on GitHub though. Here’s what the final animation looks like:

The final animation needs 3 times more code than the basic animation but feels a lot more natural. Motion design is extremely important for user experience. Many mobile apps have a great user experience tanks to motion design but it is still very rare to see it for web apps, even web apps with full Javascript clients written with Angular.js, Ember.js or React.js.

I chose to use TransitionGroup and CSSTransitionGroup for this tutorial because the official documentation is a bit short and having a long step-by-step article seems appropriate. But they have limits and there are alternatives that could suit your needs better like react-flip-move and react-motion. I will add another couple articles on the subject as I think it’s an important topic for the React.js community.


Originally published at www.nicolasmerouze.com.