Playing with React Native Animations

Author

Spencer Carli

Developer, cat dad, and devout pizza lover. Teaching at React Native School and building apps with Handlebar Labs.

For my first post of the year I wanted to keep it light and experiment a little bit with the React Native Animated API. We’ll create a few bars that animate in and constantly change their size. Here’s what our end result will be

Please note that any choppiness is a result of recording the GIF (video at end of post as well).

Interested in giving it a shot? Let’s get to it.

Project Setup

All the code is available on Github but if you want to follow along here’s what you need to do to get started.

react-native init RNBarGraphExample

Then create an app/index.js and app/AnimatedBar.js and copy and paste the following into them to get started.

app/AnimatedBar.js

// app/AnimatedBar.js

import React, { Component } from 'react';
import { Animated } from 'react-native';
import randomcolor from 'randomcolor';

class AnimatedBar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      color: randomcolor(),
    };
  }

  render() {
    return <Animated.View />;
  }
}

export default AnimatedBar;

app/index.js

// app/index.js

import React, { Component } from 'react';
import { View, Dimensions } from 'react-native';
import AnimatedBar from './AnimatedBar';

const window = Dimensions.get('window');
const DELAY = 100;

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      data: [],
    };
  }

  render() {
    return (
      <View
        style={{
          flex: 1,
          backgroundColor: '#F5FCFF',
          justifyContent: 'center',
        }}
      >
        <View>
          {this.state.data.map((value, index) => (
            <AnimatedBar value={value} delay={DELAY * index} key={index} />
          ))}
        </View>
      </View>
    );
  }
}

export default App;

Both of these files are very simple and will result in a light blue screen in your app (nothing else). Give them a quick once over to understand what’s going on.

Finally, we want to actually use these files in our app — to do so replace index.ios.js and index.android.js with the following.

index.*.js

import { AppRegistry } from 'react-native';
import App from './app/index';

AppRegistry.registerComponent('RNBarGraphExample', () => App);

Final step, which is purely convenience, is to install randomcolor via npm.

Creating Data

Before we can render anything, we need some data. We’ll keep this very simple and create 10 data elements with a width between 0 and the screen width — this will all take place in app/index.js

app/index.js

//...

class App extends Component {
  // ...

  componentDidMount() {
    this.generateData();
  }

  generateData = () => {
    const data = [];
    for (let i = 0; i < 10; i++) {
      data.push(Math.floor(Math.random() * window.width));
    }

    this.setState({
      data,
    });
  };

  // ...
}

export default App;

With this when our component mounts it will create 10 elements of varying widths. Now let’s quickly create some static bars so we can start seeing things. In app/AnimatedBar.js we’ll just set some static styles and apply them to our view.

app/AnimatedBar.js

// ...
class AnimatedBar extends Component {
  // ...

  render() {
    const barStyles = {
      backgroundColor: this.state.color,
      height: 40,
      width: this.props.value,
      borderTopRightRadius: 4,
      borderBottomRightRadius: 4,
    };

    return <Animated.View style={barStyles} />;
  }
}

export default AnimatedBar;

Note that the background color for each bar is coming from the state, which will be a random color and persist between any re-renders of that components. You should see something like this now

1

Your colors will vary.

Cool — we see something! But it’s boring…

Animation Upon Entry

So now we want to animate the bar width when it’s first rendered. It’s visually pleasing and a quick win. To do this, we need to create a new animated value in app/AnimatedBar.js and then use that value to dictate the width of our bar.

app/AnimatedBar.js

// ...
class AnimatedBar extends Component {
  constructor(props) {
    super(props);

    this._width = new Animated.Value(0); // Added
    this.state = {
      color: randomcolor(),
    };
  }

  render() {
    const barStyles = {
      backgroundColor: this.state.color,
      height: 40,
      width: this._width, // Changed
      borderTopRightRadius: 4,
      borderBottomRightRadius: 4,
    };

    return <Animated.View style={barStyles} />;
  }
}

export default AnimatedBar;

With that our bar would always have a value of 0 so now we need to animate it from 0 to the value you pass into the AnimatedBar instance — we’ll do this in the componentDidMount in AnimatedBar . We’re leveraging the Animated.timing function to change the value from 0 to the value passed in. You could also use Animated.spring if you want to.

app/AnimatedBar.js

// ...

class AnimatedBar extends Component {
  // ...

  componentDidMount() {
    const { value } = this.props;
    Animated.timing(this._width, {
      toValue: value,
    }).start();
  }

  // ...
}

export default AnimatedBar;

This function takes our animated value as the first parameter and a configuration object as the second. Initially the width is 0 and we’re telling it to gradually progress from 0 to X (where X is the random width value passed to the component via props). You should now see something like this when reloading the app.

null

Delaying the Animation

What we did above can be a bit busy — there is a lot going on at once. Let’s set it up to cascade down with a slight delay between rendering each bar, shall we?

If you remember from earlier, we’re also sending a delay to our AnimatedBar which is simply a static number (100 in this case) * the index of that value in the area. Therefore the delay for the first element is 100 * 0 = 0 , the second is 100 * 1 = 100 , …, 100 * 9 = 900 . So the first element will start immediately and the last won’t start until nearly a second later.

With that in mind let’s use the delay value! Again we’ll be working in our componentDidMount and, in addition to Animated.timing , we’ll be using Animated.sequence , and Animated.delay .

Animated.sequence allows us to sequence various animation functions and Animated.delay just lets us delay successive animated events.

Animated.timing actually has a delay option you can pass that does the same thing and is more succinct but I wanted to experiment with different functions to learn about them, and share with you.

app/AnimatedBar.js

// ...

class AnimatedBar extends Component {
  // ...

  componentDidMount() {
    const { delay, value } = this.props;
    Animated.sequence([
      Animated.delay(delay),
      Animated.timing(this._width, {
        toValue: value,
      }),
    ]).start();
  }

  // ...
}

export default AnimatedBar;

So you can see here that we’re passing an array of Animated functions to Animated.sequence which will call them in order. For the delay we’re using the delay that was passed to the component.

null

And here we see the same animation as before just cascading down with a slight delay between each! Simple change but I think it adds a lot to the animation…. and we learned more APIs!

Constant Animation

Right now our app only renders when the data is initially loaded — for many applications this will be adequate but let’s have a little more fun and have it constantly updating. The core animation is in place already — we just need to abstract some of our AnimatedBar code to also run when the component is updated. We’ll create a new function for that and call it when the component is first mounted as well as when componentWillReceiveProps is called.

app/AnimatedBar.js

// ...

class AnimatedBar extends Component {
  // ...

  componentDidMount() {
    this.animateTo(this.props.delay, this.props.value);
  }

  componentWillReceiveProps(nextProps) {
    this.animateTo(nextProps.delay, nextProps.value);
  }

  animateTo = (delay, value) => {
    Animated.sequence([
      Animated.delay(delay),
      Animated.timing(this._width, {
        toValue: value,
      }),
    ]).start();
  };

  // ...
}

export default AnimatedBar;

Great! Now we’ll set up an interval to generate new data every 1 second.

app/index.js

// ...

class App extends Component {
  // ...

  componentDidMount() {
    this.generateData();
    this.interval = setInterval(() => {
      this.generateData();
    }, 1000);
  }

  componentWillUnmount() {
    clearInterval(this.interval);
  }

  // ..
}

export default App;

And there we have our full animation! Below is a video that better shows the movement