Author avatar

Manujith Pallewatte

Animations in React

Manujith Pallewatte

  • Jul 10, 2020
  • 15 Min read
  • 683 Views
  • Jul 10, 2020
  • 15 Min read
  • 683 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

Animations are a neat way to give a finishing touch to your web app. As developers, we often keep animations towards the end of the implementation and focus on the functional spec. A key reason for this is the feeling of complexity that comes with animations. But in reality, most animations are not more than a couple of code lines away. And once added, animations dramatically improve the UX of the overall web app, making it more intuitive and appealing. Thus, it's beneficial to know implementation techniques for at least the basic animations.

In this guide, we'll explore animations using a simple tab pane example. We often get to use tabbed navigation in our views. In the jQuery days, our tabs would have worked perfectly when the necessary libraries were imported. But with React, we need to add a bit more work to make the UX intuitive and appealing to the user.

To keep things simple, we will be using the tabs component from Bootstrap. Please note that we won't be using React-Bootstrap, which has React component implementations for each Bootstrap component, but rather the plain Bootstrap CSS styling. First, create an app, and add Bootstrap with npm install --save bootstrap. Then we'll add the following tabbed component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// App.js

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';

function TabComponent() {
    const [currentTab, setCurrentTab] = useState(1);

    return (
        <div className="tabs">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===1 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(1)} href="#"
                    >
                        Tab 01
                    </a>
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===2 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(2)} href="#"
                    >
                        Tab 02
                    </a>                
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===3 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(3)} href="#"
                    >
                        Tab 03
                    </a>                
                </li>
            </ul>
            <div class="tab-content">
                {currentTab === 1 &&
                    <div id="home" class="tab-pane">
                        <h3>Tab 01</h3>
                        <p>Some content</p>
                    </div>
                }
                {currentTab === 2 &&
                    <div id="menu1" class="tab-pane">
                        <h3>Tab 02</h3>
                        <p>Some content</p>
                    </div>
                }
                {currentTab === 3 &&
                    <div id="menu1" class="tab-pane">
                        <h3>Tab 03</h3>
                        <p>Some content</p>
                    </div>
                }
            </div>
        </div>
    )
}

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <TabComponent />
      </header>
    </div>
  );
}

export default App;
jsx

When you run the app, you can see that although the tabs work, they does not have the feel of a native tab component. Ideally, when a tab is clicked, the content should slide in depending on the direction of the tab, rather than abruptly disappearing and appearing, as is happening here.

Animating with Pure CSS

CSS transitions are an excellent starting point for introducing animations for your app. Given wide browser support, hardware rendering capabilities, and ease of use, pure CSS transitions should be your first choice in implementing animations. But you'll soon observe that when combined with React, CSS transitions have a few limitations.

CSS transitions are widely used in providing UX feedback on user actions such as hover and focus. You will first make the tab navigation more intuitive to the user by animating it. In general, the current trend with tabs is to denote the currently active tab using a colored border at the bottom of the active tab. The same styling can be applied to the on-hover event. The following code demonstrates an example of this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.nav-tabs{
  border: none;
}

.nav-tabs .nav-item .nav-link{
  border: none;
}

.nav-tabs .nav-item .nav-link.active{
  border-bottom: 3px solid #E40046;
}

.nav-tabs .nav-item .nav-link:hover{
  border-bottom: 3px solid #E40046;
}

.tab-content{
  padding: 10px;
}

.tab-pane{
  display: block;
}
css

Although it responds to the user's hover action, the response is not intuitive or smooth. It lacks the quality of a proper animation. To overcome this, you can try the following simple transition and see the change in experience.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.nav-tabs{
  border: none;
}

.nav-tabs .nav-item .nav-link{
  border: none;
  position: relative;
}

.nav-tabs .nav-item .nav-link::before{
  content: '';
  position: absolute;
  width:0;
  height:3px;
  background:#E40046;
  position:absolute;
  left: 50%;
  bottom: -3px;
  -webkit-transition: all 0.3s ease-out;
  transition: all 0.3s ease-out;
}

.nav-tabs .nav-item .nav-link.active{
  border-bottom: 3px solid #E40046;
}

.nav-tabs .nav-item .nav-link:hover::before{
  left: 0;
  width: 100%;
}

.nav-tabs .nav-item .nav-link.active::before{
  width: 0;
}

.tab-content{
  padding: 10px;
}

.tab-pane{
  display: block;
}
css

It's apparent that the UX improves dramatically even with a simple tweak. Next, the focus is on the tab content. As discussed earlier, the ideal scenario would be to slide the tab content when the user changes the active tab. You can try extending the above CSS tweak to the tab content by defining an active class and assigning it to the active tab.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
.nav-tabs{
  border: none;
}

.nav-tabs .nav-item .nav-link{
  border: none;
  position: relative;
}

.nav-tabs .nav-item .nav-link::before{
  content: '';
  position: absolute;
  width:0;
  height:3px;
  background:#E40046;
  position:absolute;
  left: 50%;
  bottom: -3px;
  -webkit-transition: all 0.3s ease-out;
  transition: all 0.3s ease-out;
}

.nav-tabs .nav-item .nav-link.active{
  border-bottom: 3px solid #E40046;
}

.nav-tabs .nav-item .nav-link:hover::before{
  left: 0;
  width: 100%;
}

.nav-tabs .nav-item .nav-link.active::before{
  width: 0;
}

.tab-content{
  padding: 10px;
}

.tab-pane{
  display: block;
  position: relative;
  left: 100%;
  -webkit-transition: all 0.3s ease-out;
  transition: all 0.3s ease-out;
}

.tab-pane.active{
  left: 0;
}
css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
function TabComponent() {
    const [currentTab, setCurrentTab] = useState(1);

    return (
        <div className="tabs">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===1 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(1)} href="#"
                    >
                        Tab 01
                    </a>
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===2 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(2)} href="#"
                    >
                        Tab 02
                    </a>                
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===3 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(3)} href="#"
                    >
                        Tab 03
                    </a>                
                </li>
            </ul>
            <div class="tab-content">
                {currentTab === 1 &&
                    <div class={`tab-pane ${currentTab===1 ? 'active' : ''}`}>
                        <h3>Tab 01</h3>
                        <p>Some content</p>
                    </div>
                }
                {currentTab === 2 &&
                    <div class={`tab-pane ${currentTab===2 ? 'active' : ''}`}>
                        <h3>Tab 02</h3>
                        <p>Some content</p>
                    </div>
                }
                {currentTab === 3 &&
                    <div class={`tab-pane ${currentTab===3 ? 'active' : ''}`}>
                        <h3>Tab 03</h3>
                        <p>Some content</p>
                    </div>
                }
            </div>
        </div>
    )
}
jsx

When you run the code above, observe that the tab simply appears rather than animating as expected. The reason is the way DOM acts. In the earlier case, DOM elements were not changed when the hover event was fired. Hence, the browser rendered the animation as expected. But in this case, the new tab content initially does not exist in the DOM. It's shown in the DOM using the React conditional rendering. Hence, the browser simply skips the animation and directly shows the newly added content. This problem creates the need for the support of an external library to provide the desired effect.

Animating with ReactTransitionGroup

There are several mature libraries available for supporting animations in React, such as:

ReactTransitionGroup will be used in this guide, given that it's the de facto library recommended by React. But these tips can be easily applied for other libraries. First, install it using npm install [email protected] --save.

The following code shows how the slide-in effect can be achieved using ReactTransitionGroup. It involves wrapping your React component with ReactCSSTransitionGroup and specifying the desired effects using CSS. This enables you to use the power of CSS transitions while applying them to React.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import { CSSTransitionGroup } from 'react-transition-group'

function TabComponent() {
    const [currentTab, setCurrentTab] = useState(1);

    return (
        <div className="tabs">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===1 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(1)} href="#"
                    >
                        Tab 01
                    </a>
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===2 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(2)} href="#"
                    >
                        Tab 02
                    </a>                
                </li>
                <li class="nav-item">
                    <a 
                        class={`nav-link ${currentTab===3 ? 'active' : ''}`} 
                        onClick={() => setCurrentTab(3)} href="#"
                    >
                        Tab 03
                    </a>                
                </li>
            </ul>
            
            <div class="tab-content">
                <CSSTransitionGroup
                    transitionName="slidein"
                    transitionEnterTimeout={500}
                    transitionLeaveTimeout={300}
                >
                    
                        {currentTab === 1 &&
                            <div class={`tab-pane ${currentTab===1 ? 'active' : ''}`}>
                                <h3>Tab 01</h3>
                                <p>Some content</p>
                            </div>
                        }
                        {currentTab === 2 &&
                            <div class={`tab-pane ${currentTab===2 ? 'active' : ''}`}>
                                <h3>Tab 02</h3>
                                <p>Some content</p>
                            </div>
                        }
                        {currentTab === 3 &&
                            <div class={`tab-pane ${currentTab===3 ? 'active' : ''}`}>
                                <h3>Tab 03</h3>
                                <p>Some content</p>
                            </div>
                        }
                </CSSTransitionGroup>
            </div>
        </div>
    )
}
jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
.nav-tabs{
  border: none;
}

.nav-tabs .nav-item .nav-link{
  border: none;
  position: relative;
}

.nav-tabs .nav-item .nav-link::before{
  content: '';
  position: absolute;
  width:0;
  height:3px;
  background:#E40046;
  position:absolute;
  left: 50%;
  bottom: -3px;
  -webkit-transition: all 0.3s ease-out;
  transition: all 0.3s ease-out;
}

.nav-tabs .nav-item .nav-link.active{
  border-bottom: 3px solid #E40046;
}

.nav-tabs .nav-item .nav-link:hover::before{
  left: 0;
  width: 100%;
}

.nav-tabs .nav-item .nav-link.active::before{
  width: 0;
}

.tab-content{
  padding: 10px;
}

.tab-pane{
  display: block;
  position: relative;
}

.slidein-enter {
  left: 100%;
  transition: all 0.3s ease-out;
}

.slidein-enter.slidein-enter-active {
  left: 0;
  transition: all 0.3s ease-out;
}

.slidein-leave {
  left: -100vw;
  transition: all 0.3s ease-out;
}

.slidein-leave.slidein-leave-active {
  left: -100vw;
  transition: all 0.3s ease-out;
}
css

Refer to the official React guide on animation for an in-depth explanation of the inner workings of the library. But with the above information, you should be able to apply animations to a wide variety of cases.

Conclusion

In this guide, we studied two different ways of applying animations to React. Using pure CSS transitions is the preferred method given the ease of development. Yet we observed some instances where CSS transitions fail to deliver when used alongside React. For these instances, we need to use a third-party library that does the heavy lifting for us. There are several libraries available, and we tried ReactTransitionGroup. Although this guide gives you an introduction into the world of animation in React, you'll need to experiment with several libraries until you find the perfect fit for your use cases. For additional reading, refer to this medium article.

1