React Native Animations Using the Animated API

Getting up and running with React Native Animations

Looking to learn React Native? Check out React Native Training.

The recommended way to animate in React Native for most cases is by using the Animated API.

The final github repo for this project is here.

There are three main Animated methods that you can use to create animations:

  1. Animated.timing() — Maps time range to easing value.
  2. Animated.decay() — starts with an initial velocity and gradually slows to a stop.
  3. Animated.spring() — Simple single-spring physics model (Based on Rebound and Origami). Tracks velocity state to create fluid motions as the toValue updates, and can be chained together.

We will be covering Animated.timing() and Animated.spring() as they are the most useful in my experience.

Along with these three Animated methods, there are three ways to call these animations along with calling them individually. We will be covering all three of these as well:

  1. Animated.parallel() — Starts an array of animations all at the same time.
  2. Animated.sequence() — Starts an array of animations in order, waiting for each to complete before starting the next. If the current running animation is stopped, no following animations will be started.
  3. Animated.stagger() — Array of animations may run in parallel (overlap), but are started in sequence with successive delays. Very similar to Animated.parallel() but allows you to add delays to the animations.

1. Animated.timing()

The first animation we will be creating is this spinning animation using Animated.timing().

// Example implementation:
Animated.timing(
someValue,
{
toValue: number,
duration: number,
easing: easingFunction,
delay: number
}
)

This type of infinite animation can be useful to use when creating loading indicators, and is one of the more useful animations that I’ve used in many of my React Native projects. This concept can also be used to create infinite animations of other types such as scaling up and down or some other type to indicate loading.

To get started, we need to either start with a new React Native project or with a blank existing React Native project. To get started with a new project, type react-native init and the name of the project in the folder in which you will be working in and then cd into that directory:

react-native init animations
cd animations

Now that we are in this folder, open either the index.android.js or index.ios.js file.

Now that we have a new project created, the first thing we will need to do is import Animated, Image, and Easing from react-native below View that is already being imported:

import {
AppRegistry,
StyleSheet,
Text,
View,
Animated,
Image,
Easing

} from 'react-native'

Animated is the library we will be using to create the animations, and ships with React Native.

Image is needed so we can create an image in our UI.

Easing is a module that also ships with React Native. It allows us to use various predefined easing methods such as linear, ease, quad, cubic, sin, elastic, bounce, back, bezier, in, out, inout, and others. We will be using linear as to have a consistent linear motion. You will have a better idea of how to implement them after this section is finished.

Next, we need to set an initial animated value for our spinning value. To do this, we will set the value in our constructor:

constructor () {
super()
this.spinValue = new Animated.Value(0)
}

We declare spinValue as a new Animated.Value and pass in 0 (zero).

Next, we need to create a spin method and call this method on componentDidMount to get it going when the app loads:

componentDidMount () {
this.spin()
}
spin () {
this.spinValue.setValue(0)
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 4000,
easing: Easing.linear
}
).start(() => this.spin())
}

spin() — This method does the following:

  1. Sets this.spinValue back to zero
  2. Calls the Animated.timing method and animates this.spinValue to a value of 1 with a duration of 4000 milliseconds and an easing of Easing.linear. Animated.timing takes two arguments, a value (this.spinValue) and a config object. This config object can take a toValue, a duration, an easing method, and a delay.
  3. We call start() on this Animated.timing method, and pass in a callback of this.spin which will be called when the animation is completed, basically creating an infinite animation. start() takes a completion callback that will be called when the animation is done. If the animation is done because it finished running normally, the completion callback will be invoked with {finished: true}, but if the animation is done because stop was called on it before it could finish (e.g. because it was interrupted by a gesture or another animation), then it will receive {finished: false}.

Now that our methods are set up, we need to render the animation in our UI. To do so, we need to update our render method:

render () {
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<View style={styles.container}>
<Animated.Image
style={{
width: 227,
height: 200,
transform: [{rotate: spin}] }}
source={{uri: 'https://s3.amazonaws.com/media-p.slid.es/uploads/alexanderfarennikov/images/1198519/reactjs.png'}}
/>
</View>
)
}
  1. We create a variable named spin. In this variable we call interpolate() on this.spinValue. interpolate() is a method that is available to be called on any Animated.Value. interpolate is a method that interpolates the value before updating the property, e.g. mapping 0–1 to 0–10. So in our example, we need to map 0 (zero) degrees to 360 degrees numerically, using the numbers zero to one, and this method easily allows us to do this. We pass in an inputRange and outputRange array, and pass in [0,1] as the inputRange and [‘0deg’, ‘360deg’] as the outputRange.
  2. We return a View with a style of container, and an Animated.Image (React logo) with a height, width, and a transform property in which we attach our spin value to the rotate property, which is where the animation takes place:
transform: [{rotate: spin}]

Finally, we have the container style to center everything:

const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})

That’s it, the animation should be working now!

Here is the final code for this animation with a working example.

More on Easing

Here is a link to the source code for Easing, where you can see each of the easing methods.

I have set up an example project with most of the easing animations implemented for you to take a look at and play with, to see how they are implemented here. The gist is also below.

Here are the easings implemented in an example project on RNPlay.

Here is the gist for the different easings.

2. Animated.timing examples

Now that we know the basics of Animated.timing, let’s take a look at a few more examples of how to use Animated.timing along with interpolate and declare some other animations.

In the next example, we will declare a single animation value, this.animatedValue, and use the single animated value along with interpolate to create multiple animations, animating the following style properties:

  1. marginLeft
  2. opacity
  3. fontSize
  4. rotateX

To get started, either begin with a new branch or clear out our old code from the last project.

The first thing we will do is create the animated value that we will be using for these animations in the constructor:

constructor () {
super()
this.animatedValue = new Animated.Value(0)
}

Next, we create the animate method and call it in componentDidMount() :

componentDidMount () {
this.animate()
}
animate () {
this.animatedValue.setValue(0)
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 2000,
easing: Easing.linear
}
).start(() => this.animate())
}

In the render method, we create 5 different interpolated value variables:

render () { 
const marginLeft = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 300]
})
const opacity = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 0]
})
const movingMargin = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 300, 0]
})
const textSize = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [18, 32, 18]
})
const rotateX = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['0deg', '180deg', '0deg']
})
...
}

interpolate is a very powerful method, allowing us to use this.animatedValue , a single animated value, in many ways. Because the value simply changes from zero to one, we are able to interpolate this property for styling opacity, margins, text sizes, and rotation properties.

We then return Animated.View and Animated.Text components implementing these new variables:


return (
<View style={styles.container}>
<Animated.View
style={{
marginLeft,
height: 30,
width: 40,
backgroundColor: 'red'}} />
<Animated.View
style={{
opacity,
marginTop: 10,
height: 30,
width: 40,
backgroundColor: 'blue'}} />
<Animated.View
style={{
marginLeft: movingMargin,
marginTop: 10,
height: 30,
width: 40,
backgroundColor: 'orange'}} />
<Animated.Text
style={{
fontSize: textSize,
marginTop: 10,
color: 'green'}} >
Animated Text!
</Animated.Text>
<Animated.View
style={{
transform: [{rotateX}],
marginTop: 50,
height: 30,
width: 40,
backgroundColor: 'black'}}>
<Text style={{color: 'white'}}>Hello from TransformX</Text>
</Animated.View>
</View>
)

We also update our container styling:

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 150
}
})

The animation should be working now!

Here is the final code for this animation with a working example.

3. Animated.spring()

Next, we will be creating an animation using the Animated.spring() method.

// Example implementation:
Animated.spring(
someValue,
{
toValue: number,
friction: number
}
)

We can keep going from the same project, we just need to update a few things. In our constructor, let’s create a value called springValue and set it’s value to .3:

constructor () {
super()
this.springValue = new Animated.Value(0.3)
}

Next, let’s delete the animate() method and componentDidMount() method and create a new method called spring():

spring () {
this.springValue.setValue(0.3)
Animated.spring(
this.springValue,
{
toValue: 1,
friction: 1
}
).start()
}
  1. We set the springValue to .3 if it’s not already set to .3
  2. We call Animated.spring, passing in two arguments: a value to animate and a config object. The config object can take any of the following arguments: toValue (number), overshootClamping (boolean), restDisplacementThreshold (number), restSpeedThreshold (number), velocity (number), bounciness (number), speed (number), tension (number), and friction (number). The only required value is toValue, but friction and tension can help you get more control over the spring animation.
  3. We call start() to start the animation.

Now that the animation is set up, let’s attach the animation to a click event in our view, and the animation itself to the same React logo we used before:

<View style={styles.container}>
<Text
style={{marginBottom: 100}}
onPress={this.spring.bind(this)}>Spring</Text>
<Animated.Image
style={{ width: 227, height: 200, transform: [{scale: this.springValue}] }}
source={{uri: 'https://s3.amazonaws.com/media-p.slid.es/uploads/alexanderfarennikov/images/1198519/reactjs.png'}}/>
</View>
  1. We return a Text component attached to the spring() method to an onPress event.
  2. We return the Animated image and attach this.springValue to the scale property.

The spring animation should be working now!

Here is the final code for this animation with a working example.

4. Animated.parallel()

Animated.parallel() starts an array of animations all at the same time.

Let’s take a look at the api and see how this works:

// API
Animated.parallel(arrayOfAnimations)
// In use:
Animated.parallel([
Animated.spring(
animatedValue,
{
//config options
}
),
Animated.timing(
animatedValue2,
{
//config options
}
)
])

To get started, let’s go ahead and create the three animated values we will need in our constructor:

constructor () {
super()
this.animatedValue1 = new Animated.Value(0)
this.animatedValue2 = new Animated.Value(0)
this.animatedValue3 = new Animated.Value(0)
}

Next, we create our animate method and call it in componendDidMount() :

componentDidMount () {
this.animate()
}
animate () {
this.animatedValue1.setValue(0)
this.animatedValue2.setValue(0)
this.animatedValue3.setValue(0)
const createAnimation = function (value, duration, easing, delay = 0) {
return Animated.timing(
value,
{
toValue: 1,
duration,
easing,
delay
}
)
}
Animated.parallel([
createAnimation(this.animatedValue1, 2000, Easing.ease),
createAnimation(this.animatedValue2, 1000, Easing.ease, 1000),
createAnimation(this.animatedValue3, 1000, Easing.ease, 2000)
]).start()
}

In the animate method, we set the values of all three animated values back to zero. We then create a function called createAnimation() which returns a new animation , taking in the value, duration, easing, and delay as arguments. If no delay is passed in, we set it to zero.

We then call Animated.parallel() passing in the three animations we want to create using createAnimation().

In our render method, we next need to set up our interpolated values:

render () {
const scaleText = this.animatedValue1.interpolate({
inputRange: [0, 1],
outputRange: [0.5, 2]
})
const spinText = this.animatedValue2.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '720deg']
})
const introButton = this.animatedValue3.interpolate({
inputRange: [0, 1],
outputRange: [-100, 400]
})
...
}

1. scaleText we interpolate our values to be output as a range from 0.5 to 2, we will use this value to scale our text from .5 to 2.

2. spinText — we interpolate our values to be output as a range of 0 degrees to 720 degrees, essentially spinning the item two times.

3. introButton — we interpolate out values to be output as a range of -100 to 400, and will use this as a margin property in our View.

Finally, we render a main View with three Animated.Views:

<View style={[styles.container]}>
<Animated.View
style={{ transform: [{scale: scaleText}] }}>
<Text>Welcome</Text>
</Animated.View>
<Animated.View
style={{ marginTop: 20, transform: [{rotate: spinText}] }}>
<Text
style={{fontSize: 20}}>
to the App!
</Text>
</Animated.View>
<Animated.View
style={{top: introButton, position: 'absolute'}}>
<TouchableHighlight
onPress={this.animate.bind(this)}
style={styles.button}>
<Text
style={{color: 'white', fontSize: 20}}>
Click Here To Start
</Text>
</TouchableHighlight>
</Animated.View>
</View>

We use scaleText to scale the first View, spinText to spin the second View, and introButton to animate the margin of the third View.

When animate() is called, all three animations run in parallel.

The parallel animations should be working now!

Here is the final code for this animation with a working example.

5. Animated.Sequence()

Let’s take a look at the api and see how this animation works:

// API
Animated.sequence(arrayOfAnimations)
// In use
Animated.sequence([
Animated.timing(
animatedValue,
{
//config options
}
),
Animated.spring(
animatedValue2,
{
//config options
}
)
])

Like Animated.parallel(), Animated.sequence() takes an array of animations. Animated.sequence() runs an array of animations in order, waiting for each to complete before starting the next.

Because the apis for Animated.sequence() and Animated.parallel() are so similar, taking an array of animations, I am not going to repeat the walkthrough of each method.

The main thing to notice here that is different is that we are creating our Animated.Values with a loop since we are animating so many values. We are also rendering our Animated.Views with a map function returning a new Animated.View for each item in the array.

The sequence of animations should be working now!

Here is the final code for this animation with a working example.

6. Animated.Stagger()

Let’s take a look at the api and see how this animation works:

// API
Animated.stagger(delay, arrayOfAnimations)
// In use:
Animated.stagger(1000, [
Animated.timing(
animatedValue,
{
//config options
}
),
Animated.spring(
animatedValue2,
{
//config options
}
)
])

Like Animated.parallel() and Animated.sequence(), Animated.Stagger also takes an array of animations, but these animations are started in sequence with successive delays.

The main difference here is the first argument, the delay that will be applied to each animation.

The staggered animations should be working now!

Here is the final code for this animation with a working example.

To view the final repo, click here.

My Name is Nader Dabit . I am a software consultant and trainer and the founder of React Native Training where we teach developers at top companies around the world how to quickly get up and running with React Native.
If you like React and React Native, checkout out our podcast — React Native Radio on Devchat.tv.
Also, check out my book, React Native in Action now available from Manning Publications
If you enjoyed this article, please recommend and share it! Thanks for your time