Learn React VR (Chapter 4 | Transitions and Animations)

Prerequisites

Chapter 1 | Hello Virtual World
Chapter 2 | Panoramic Road Trip
Chapter 3 | Outdoor Movie Theater

In addition, I am going to assume knowledge of basic CSS animations. If you do not already know this, here’s an introduction video I created.

Scope of This Chapter

In the previous chapter, we were able to create a mini VR app called Outdoor Movie Theater that allowed us to how to work with 360-degree sound and video in React VR.

We had created a Main Menu scene and a Movie Theater scene. In this chapter, we are going to start off by adding a Scene Select scene. We will then have 3 scenes and each scene should lead to another (Main Menu → Scene Select → Movie Theater). We will be adding the logic to transition between each scene. After that, we are going to explore in detail how animation works in React VR. Finally, we will add animation to our Outdoor Movie Theater app to finish it off.

Scene Transitions

Adding Scene Select

First, let’s add a scene component called SceneSelect. In this component, we ultimately want to render a list of movie scenes that a user could select as well as a title. In our case, we will keep it simple and have a button for the fireplace movie scene that we got up and running in the previous chapter:

Within the Scenes folder, go ahead and create a new file called SceneSelect.js.

Next, we can add the following code:

import React from 'react';
import {
View,
Text,
VrButton
} from 'react-vr';
import SceneSelectMenu from './Layouts/SceneSelectMenu.js';
//Scene
class SceneSelect extends React.Component {
render() {
return (
<SceneSelectMenu/>
)
}
}
module.exports = SceneSelect;

Here, we are nesting a component that we will make next called SceneSelectMenu. This will be a layout component that has the Flexbox layout for the Title and Button elements within the scene.

Let’s make another file for this layout component within the Layouts folder called SceneSelectMenu.js.

For now, let’s add the following code:

import React from 'react';
import {
View,
Text,
VrButton
} from 'react-vr';

//Layout
class SceneSelectMenu extends React.Component {
render() {
return (
<View
style={{
flex: 1,
width: 3,
flexDirection: 'column',
alignItems: 'stretch',
layoutOrigin: [0.5, 0.5],
transform: [{translate: [0, 0, -5]}]
}}
>
      </View>
)
}
}
module.exports = SceneSelectMenu;

Here, we have added the Flexbox layout that we alluded to earlier. However, we have nothing nested within this layout.

What should be nested?

We want to nest a Title and Button component so we get the following:

In order to do this, we want to write as little code as possible. If our Main Menu scene is using a Title and Button component, we want to be able to reuse that for this Scene Select scene.

In order to reuse our Title and Button, we need the text for both of those components to be dynamic and not static like we currently have:

//Button.js
<Text style={{fontSize: 0.2, textAlign: 'center'}}>
Select a Movie
</Text>
//Title.js
<Text style={{...}}>
Outdoor Movie Theater
</Text>

We want our button component to have a text of “Fireplace” in our Scene Select scene and a text of “Select a Movie” in our Main Menu scene.

We want our Title component to have a text of “Scene Select” in our Scene Select scene and a text of “Outdoor Movie Theater” in our Main Menu scene.

To resolve this, we will pass down the texts for the Title and Button components as props from each scene component. The values for these props will be different for each scene.

Let’s get started with this by first opening index.vr.js.

Here, we are going to add the nesting of our SceneSelect component:

<View>
<Pano source={asset('fort-night.jpg')}>
<Sound
volume={0.8}
loop = {true}
source={{mp3: asset('fort-night-atmosphere.mp3')}}
/>
</Pano>
<MovieTheater/>
<SceneSelect/>
</View>

Eventually, we will be adding conditional rendering since we can’t display these scenes at the same time. Let’s ignore that for now and pass down the props for the Title and Button components:

<MainMenu text={"Outdoor Movie Theater"} buttonText={"Select a Movie"}/>
<SceneSelect text={"Scene Select"} buttonText={"Fireplace"}/>

In the code above, we are passing down a prop called text for the Title and buttonText for the Button. Notice that the values are different for each scene.

Next, we can work our way down until these props are passed down to the Title and Button components.

We pass the props down to the layout components:

SceneSelect.js

<SceneSelectMenu text={this.props.text} buttonText={this.props.buttonText} />

MainMenu.js

<MainMenuContainer text={this.props.text} buttonText={this.props.buttonText} />

Then, we pass them down to the Title and Button elements:

SceneSelectMain.js

import React from 'react';
import {
View,
Text,
VrButton
} from 'react-vr';
import Title from './Elements/Title.js';
import Button from './Elements/Button.js';
//Layout
class SceneSelectMenu extends React.Component {
render() {
return (
<View
style={{
flex: 1,
width: 3,
flexDirection: 'column',
alignItems: 'stretch',
layoutOrigin: [0.5, 0.5],
transform: [{translate: [0, 0, -5]}]
}}
>
<Title text={this.props.text}/>
<Button buttonText={this.props.buttonText}/>
</View>
)
}
}
module.exports = SceneSelectMenu;

MainMenuContainer.js

import React from 'react';
import {
Text,
View,
} from 'react-vr';
import Title from './Elements/Title.js';
import Button from './Elements/Button.js';
//Layout
class MainMenuContainer extends React.Component {
render() {
return (
<View style={{
flex: 1,
width: 5,
flexDirection: 'column',
alignItems: 'stretch',
layoutOrigin: [0.5, 0.5],
transform: [{translate: [0, 0, -5]}]
}}>
<Title text={this.props.text}/>
<Button buttonText={this.props.buttonText}/>
</View>
)
}
}
module.exports = MainMenuContainer;

Finally, we can replace the static text and inject the props that were passed down:

Button.js

//in return
<View style={{ margin: 0.1, height: 0.3, backgroundColor: '#1AC8F7'}}>
<VrButton>
<Text style={{fontSize: 0.2, textAlign: 'center'}}>
{this.props.buttonText}
</Text>
</VrButton>
</View>

Title.js

<View style={{ margin: 0.1, height: 0.5}}>
<Text
style={{
fontSize: 0.5,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center'
}}>
{this.props.text}
</Text>
</View>

Setting Up Conditional Rendering

Sweet! We were able to add our Scene Select scene and write the logic to reuse the Title and Button components.

Now, we need to setup conditional rendering so we can control what scene is being rendered depending on values in a local state.

First, we need to add a local state to our outermost app component in index.vr.js:

constructor(){
super();
this.state={mainMenu: true, sceneSelect: false};
}
//render function here

In this local state, we are going to have 2 boolean flags.

If mainMenu is true, we will render our MainMenu component.

If sceneSelect is true and mainMenu is false, we will render our SceneSelect component.

If mainMenu and sceneSelect are false, we will render our MovieTheater component.

In our render function, let’s store the value of these properties into variables:

render() {
const mainMenu = this.state.mainMenu;
const sceneSelect = this.state.sceneSelect;
//return here
}

Then, let’s replace the current nesting of our MainMenu and SceneSelect components with the following:

{
mainMenu ? (
<MainMenu text={"Outdoor Movie Theater"} buttonText={"Select a Movie"}/>
) : (
<SceneSelect text={"Scene Select"} buttonText={"Fireplace"}/>
)
}

This code can be read as: “If mainMenu is true, render MainMenu. Else, render SceneSelect.” This is using ES6 syntax if you are not already familiar.

Let’s add a bit more so our code essentially says: ““If mainMenu is true, render MainMenu. Else, render SceneSelect if sceneSelect is true or render MovieTheater if sceneSelect is false.

{
mainMenu ? (
<MainMenu text={"Outdoor Movie Theater"} buttonText={"Select a Movie"}/>
) : (
sceneSelect ? (
<SceneSelect text={"Scene Select"} buttonText={"Fireplace"}/>
) : (
<MovieTheater/>
)
)
}

Cool! We have set up our conditional rendering.

Finalizing Transitions Between Scenes

The final step is to update the local state via event handling so we can transition between scenes.

From our Main Menu scene, we want the button to have an onClick event that will update the local state so the Select Scene scene renders.

From our Select Scene scene, we want the button to have an onClick event that will update the local state so the Select Scene scene renders.

Since the local state controlling the conditional rendering is within the app component in our index.vr.js file, we will add an event handler there for the scenarios we just mentioned:

//constructor here
updateScene(scene) {
switch(scene) {
case 2:
this.setState({ mainMenu: false, sceneSelect: true});
break;
case 3:
this.setState({ mainMenu: false, sceneSelect: false});
break;
}
}
//render here

The Button component will call this function on a user’s click. It will be passing in the scene that should be gone to next (2 = Scene Select and 3 = Movie Theater). We can update the local state to tweak the conditional rendering so we can play the requested scene.

We must now ask the following question: “If we are using the same Button component in 2 separate scenes, how will we know which scene to request?” Additionally, we must ask: “How can we call this event handler from our button if it is in a different component?”

We will pass down the event handler and an id for the current scene as props down to our Button component. The Button component will then be able to call the event handler and request the appropriate scene depending on which scene is currently rendering the button.

First, let’s pass the props down in our index.vr.js file:

<MainMenu text={"Outdoor Movie Theater"} buttonText={"Select a Movie"} updateScene={this.updateScene.bind(this)} scene={1}/>
//...
<SceneSelect text={"Scene Select"} buttonText={"Fireplace"} updateScene={this.updateScene.bind(this)} scene={2}/>

Note that MainMenu is scene 1 and SceneSelect is scene 2. MovieTheater will be the third scene.

We can then pass these props down to our layout components and finally the Button component:

MainMenu.js

<MainMenuContainer text={this.props.text} buttonText={this.props.buttonText} updateScene={this.props.updateScene} scene={this.props.scene}/>

SceneSelect.js

<SceneSelectMenu text={this.props.text} buttonText={this.props.buttonText} updateScene={this.props.updateScene} scene={this.props.scene}/>

MainMenuContainer.js

<Button buttonText={this.props.buttonText} updateScene={this.props.updateScene} scene={this.props.scene}/>

SceneSelectMenu.js

<Button buttonText={this.props.buttonText} updateScene={this.props.updateScene} scene={this.props.scene}/>

Finally, we can update Button.js with the following:

class Button extends React.Component {
render() {
const scene = this.props.scene;
return (
<View>
<View style={{ margin: 0.1, height: 0.3, backgroundColor: '#1AC8F7'}}>
{scene === 1 ? (
<VrButton onClick={() => this.props.updateScene(2)}>
<Text style={{fontSize: 0.2, textAlign: 'center'}}>
{this.props.buttonText}
</Text>
</VrButton>
) : (
<VrButton onClick={() => this.props.updateScene(3)}>
<Text style={{fontSize: 0.2, textAlign: 'center'}}>
{this.props.buttonText}
</Text>
</VrButton>
)}
</View>
</View>
)
}
}

A few things to note.

One, we stored the passed down scene prop into a variable:

const scene = this.props.scene;

Then, we used this variable for conditional rendering:

{scene === 1 ? (
//render button with event that calls updateScene and requests scene 2
) : (
//render button with event that calls updateScene and requests scene 3
)}

As the comments provided above indicate, we want to render a different buttons depending on the current scene. The two different buttons contain two different onClick event calls:

//if currently in scene 1
<VrButton onClick={() => this.props.updateScene(2)}>
//if currently in scene 2
<VrButton onClick={() => this.props.updateScene(3)}>

Note that we write the onClick just like we did in chapter 2 by using an arrow function.

Now, you can run npm start from the root of the project in command line and see that we can transition between scenes on the click of the buttons:

Understanding React VR Animation Basics

So far, everything in this chapter hasn’t been anything particular to React VR in comparison to standard React. However, it was necessary so we can get to the new and exciting information on how to use animations in React VR.

React VR has a built in library for animations called Animated.

Like all of React, Animated is a declarative API so we can tell React VR exactly what animations we want.

Let’s dive right into this by animating the Title component used in our Main Menu and Select Scene.

Animation Setup

Open up Title.js.

First things first, let’s import Animated:

import {
Text,
View,
Animated
} from 'react-vr';

We can use Animated with Image, Text, or View components. In our case, we want to add Animated to a Text component so we can do:

<Animated.Text style={{...}}>
{this.props.text}
</Animated.Text>

We just add Animated. in front of our Text component in both the opening and closing tag.

Next, we need to specify what we want to animate. Let’s do a transformation. The types of transformations are the same as in CSS. We want to translate our title along the Y axis for our transformation.

First, we can add a transform array and a translateY value within the style like so:

style={{
fontSize: 0.5,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center',
transform: [
{translateY: //value goes here}
]
}}

Since translateY is going to be changed in order to perform an animation, let’s define the value within a local state:

constructor() {
super();
this.state = { slideValue: new Animated.Value(1.5) };
}

Then, let’s use this value in our scaleY definition:

transform: [
{scaleY: this.state.slideValue}
]

We have set a property called slideValue to 0 and used it to define our translateY. In order to set a value for Animated, we use the following syntax:

new Animated.Value( //insert value here )

Therefore, it is initially the equivalent of the following:

transform: [
{translateY: 1.5}
]

In our animation which we will write shortly, we are going to slide our title down from a translated position of 1.5 to 0 (in meters). This will create the effect of our title sliding down into place.

Timing Animation

Alright! We have completed the initial setup for doing an animation in React VR. Specifically, we did a setup for a translateY transformation. We are going to apply this to a Text component, but we could also apply it to a View or Image component.

Now, we need to compose the animation. When composing an animation with Animated, there are 3 animation types:

  1. Spring
  2. Decay
  3. Timing

We will explain the each one separately.

Timing is the easiest to grasp so we will begin there.

Let’s add a Timing animation to our Title.

Timing animations will simply apply the transformation with options to control the timing.

First, we can add a componentDidMount lifecycle:

componentDidMount() {

}
//render function here

Next, we specify that we will be doing a Timing animation:

componentDidMount() {
Animated.timing(

);
}

Then, we specify the value that we will be changing for our animation (slideValue in our local state):

componentDidMount() {
Animated.timing(
this.state.slideValue
);
}

Additionally, we need to specify the options for our animation:

componentDidMount() {
Animated.timing(
this.state.slideValue,
{
toValue: 0,
duration: 1000,
delay: 1000
}
);
}

What we have just added is the heart of React VR animations. It’s a bit different than CSS animations so let’s unpack.

In CSS animations, we specify that we want to use a defined keyframes animation and some options like so:

animation: pulse 5s;
//@keyframes pulse {...}

With Animated, we replace the keyframes with toValue. toValue is the equivalent of the following keyframes animation:

@keyframes pulse {
100% {
background-color: #FF4136;
}
}

In this keyframes animation, the background-color value goes from whatever it’s initialized as (let’s say #FFFFFF)to #FF4136 by the end of the specified duration (5s in this example).

This is what the toValue specifies. It allows Animated to take the initial value of the property we want to animate and changes it to a new value in a specified duration.

Recall, the initial value is specified in the local state:

this.state = { slideValue: new Animated.Value(1.5) };

Then, we will take slideValue to a new value as specified in Animated.timing:

Animated.timing(
this.state.slideValue,
{
toValue: 0,
duration: 1000,
delay: 1000
}
).start();

We also specified options for duration and delay (in milliseconds).

Our Animated.timing animation will then be taking our title which starts off transformed along the Y axis to be 1.5 meters above the normal position to a new value of 0 when our component mounts. This will create the effect of our title sliding down into place.

The final piece to the puzzle is to start the animation:

componentDidMount() {
Animated.timing(
this.state.slideValue,
{
toValue: 0,
duration: 1000,
delay: 1000
}
).start();
}

We can now go to the local host, refresh, and see this animation in action:

Applying Easing Functions in Animated.timing

Before we get to the other 2 animation types in React VR, I am going to dedicate an additional section on applying easing functions to Animated.timing.

I thought that this would be something I could explain in just a paragraph or two. However, I ran into some unexpected hurdles that will require some additional explanation.


In addition to duration and delay, we can also specify an easing option. Here’s an example:

componentDidMount() {
Animated.timing(
this.state.slideValue,
{
toValue: 0,
duration: 1000,
delay: 1000,
easing: Easing.bounce
}
);
}

Easing is a separate API that allows us to apply predefined easing functions or custom easing functions. In the example above, bounce is a predefined easing function.

Before we dive into more details, we need to import Easing so the example in the code above will work. This will require more explanation than you may be anticipating.

As mentioned in Chapter 1, React VR is built off of React Native. There are quite a few crossovers between the two. For example, the predefined View, Text, and Image components are used in both.

Whenever there has been a crossover like this, we didn’t have to import anything from the react-native package:

import React from 'react';
import {
Text,
View,
Animated
} from 'react-vr';
//no react-native import

In the example above, Text and View are imported from react-vr even though they are also used in the react-native package.

After much frustration, it turns out that Easing has to be imported from react-native. I reached out for clarification and it turns out that while Text, View, and Image are examples of predefined components that are used in both React VR and React Native, they have their own implementation in React VR. When it comes to the Animated API, it’s using the exact same as React Native. Therefore, we can import Easing like so:

import { Easing } from 'react-native';

Now, we can see if the bounce easing function is working:

Cool!

There is official documentation for Easing on the React VR site, however, I recommend checking out the React Native documentation on it.

Here’s an overview of what we can do with the Easing API.

There are 4 predefined easing functions:

  • bounce → bouncing animation
  • ease → see the visualization here
  • elastic → spring-like animation
  • back → according to official documentation, “ a simple animation where the object goes slightly back before moving forward”

All of these can be used with the following syntax:

easing: Easing.//insert here

There are also 3 standard easing functions (not specific to React Native) that can be used:

  • linear
  • quad
  • cubic

The easiest way to visualize these is to use them in our Animated.timing animation and see for yourself. They can be used following the same syntax that is shown above.

Additionally, there are 4 more complex easing functions:

  • bezier → must be manually specified, get values here
  • circle → no custom value
  • sin → no custom value
  • exp → no custom value

Here’s an example of each being used:

//custom bezier
easing: Easing.bezier(.17,.67,1,.47)
//circle, sin, or exp
easing: //insert here

Again, you can test these out in our Animated.timing animation to get a visualization.

This completes the basics of Animated.timing.

Spring Animation

As an alternative to a timing animation which gives us control over the easing for customization, we can also use a spring animation in React VR.

Spring animation is another type of React VR animation that follows a spring-physics model. We can control the spring of the animation with 2 options:

  • friction → controls bounciness
  • tension → controls speed of spring

To switch our current animation to a spring animation (instead of a timing animation), we can start by updating the following:

Animated.spring(...) //used to be Animated.timing

Then, let’s replace the previous options with friction and tension options:

componentDidMount() {
Animated.spring(
this.state.slideValue,
{
toValue: 0,
friction: 2, //default 7
tension: 5 //default 40
}
).start();
}

The higher the tension, the faster the speed. The lower the friction, the larger the spring.

Let’s take a look:

You can play around with the values if you’d like but that’s a spring animation in a nutshell.

Decay Animation

Another animation type in React VR is a decay animation.

The idea is to take an existing velocity of a moving object and apply deceleration to make it come to a stop.

velocity and deceleration are the 2 options for the decay animation (Animated.decay).

Here’s the one example in the React VR documentation:

Animated.decay(position, {   // coast to a stop
velocity: {x: gestureState.vx, y: gestureState.vy}, // velocity from gesture release
deceleration: 0.997,
})

From my research, the React Native documentation provides the same example and explanation. Meaning, the example is meant for an example scenario on a mobile device.

This example can’t really be explained without introducing complex topics of React Native. This type of animation is also far less common and useful for the scope of this book. For these reasons, we aren’t going to explain any further but just keep in mind that it is another React VR animation type.


This concludes the basic examples of animations with React VR (animating an element from a single start and end point). We used translateY as an example but you should have all the information needed to explore doing other transformations using Animated.timing or Animated.spring.

We will look into the more advanced, yet necessary, animation concepts in the final two sections of this chapter.

Interpolation

What if we want our Title to slide down and go from an opacity of 1 to 0? How can we do this?

We could add to our local state and have a property called opacityValue:

this.state = { slideValue: new Animated.Value(1.5), opacityValue: new Animated.Value(0) };

Then, we could update our inline styling so the opacity value is bound to this new property:

style={{
fontSize: 0.5,
opacity: this.state.opacityValue,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center',
transform: [
{translateY: this.state.slideValue}
]
}}>

Finally, we could add a timing animation with a toValue of 1(I’m also going to change the slideValue animation back to a timing animation) :

componentDidMount() {
Animated.timing(
this.state.slideValue,
{
toValue: 0,
duration: 1000,
delay: 1000,
easing: Easing.ease
}
).start();
Animated.timing(
this.state.opacityValue,
{
toValue: 1,
duration: 1000,
delay: 1000,
easing: Easing.ease
}
).start();
}

If we take a look, we should see this working:

Cool…but can we do this in just one animation? We can with interpolation.


Interpolation sounds pretty intimidating, however, it’s a concept that you are already familiar with if you understand CSS animations.

Any animation has a start and end point. Let’s look at an example of going from an opacity of 0 to 1:

This can be written in a keyframes animation like so:

0% {
opacity: 0;
}
100% {
opacity: 1;
}

0% is the start of the animation and 100% is the end.

Even though there are only a start and end value specified, there will naturally be intermediate values as the opacity goes from 0 to 1 over the duration specified:

What if we wanted to control specific points from the start and end?

In a keyframes animation, we could do:

0% {
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}

Essentially, we are mapping specific values at specific points between the start and end of our animation using percentages.

Let’s say we wanted to translateX from 0px to 50px at these points in addition to changing the opacity. It would look like this in CSS:

0% {
opacity: 0;
transform: translateX(0px);
}
50% {
opacity: 0.5;
transform: translateX(25px);
}
100% {
opacity: 1;
transform: translateX(50px);
}

In this example, opacity and translateX have different ranges of values that are being changed throughout the animation.

opacity goes from 0 → 0.5 → 1.

translateX goes from 0px → 25px → 50px.

We don’t have to do any additional work on our end to have the animation work even though there are two different ranges of values.

With React VR, we have to do the additional work. This is where interpolate from the Animated API comes into play.

Let’s try to have our title translateY from a value of 1.5 to 0 as we had before. Then, we are also going to have the opacity go from a value of 0 to 1.

Using interpolate, we can do this with just one Animated.Value and one timing animation.

First, let’s create have just one property in our local state called fadeSlide:

this.state = { fadeSlide: new Animated.Value(0) };

We set the initial value to 0 and are going to write our timing function as if this value is only going to control our opacity to go from 0 to 1.

If we want our opacity to go from 0 to 1, our lifecycle will now look like this:

componentDidMount() {
Animated.timing(
this.state.fadeSlide,
{
toValue: 1,
duration: 1000,
delay: 1000,
easing: Easing.ease
}
).start();
}

Next, let’s update our inline styling to be assigned to this.state.fadeSlide:

style={{
fontSize: 0.5,
opacity: this.state.fadeSlide,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center',
transform: [
{translateY: this.state.fadeSlide}
]
}}>

Finally, we apply interpolation which is going to essentially say: “Hey React VR! We wrote out an animation to take our opacity from 0 to 1. However, we really don’t want to have to write out a separate animation for our translateX transformation. When our opacity goes from a value of 0 to 1, could you have our translateX go from a value of 1.5 to 0?”

style={{
fontSize: 0.5,
opacity: this.state.fadeSlide,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center',
transform: [
{translateY: this.state.fadeSlide.interpolate({
inputRange: [0, 1],
outputRange: [1.5, 0]
})
}
]
}}>

In the code above, we just added the following:

.interpolate({
inputRange: [0, 1],
outputRange: [1.5, 0]
})

You can think of this as interpreting our fadeSlide which is going from a value of 0 to 1 as going from a value of 1.5 to 0 only for our translateY.

This may seem awkward at first. Just think of the entire interpolation process as focusing on writing one animation for one property (like opacity) where one value is stored in the local state (even if you ultimately want to animate multiple properties). Once you are finished with this step, add the interpolate code to the other properties so that the value in the local state that is changing in the animation can be reinterpreted to be changing with different values for the other properties (like translateY). This allows you to animate multiple properties with just one value stored in the local state and one total animation.

Another use case of interpolate is for transformations that require string values. For example, we may want use rotate where we have to express the transformation in degrees:

this.state.spin.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})

You can view the official documentation to see if a transform property requires a number or string.

Composing Animation Scenes

What if we want more than one animation and we want to create an entire animation scene? How do we control the timing of these animations?

To use a visual example, think of the question as: “How can we control the timing our animations like we do when using video editing software?”

Sequence

The most important and versatile way to place animations in a timeline is to use a sequence.

A sequence contains an array of animations and by default will each animation run after the previous one completes. For example, if we had 3 animations, the animation timeline would look like this:

Let’s test this out.

First, let’s split our local state into two separate properties:

this.state = { fade: new Animated.Value(0), slide: new Animated.Value(1.5) };

Then, let’s update our inline styling to reflect this (and remove the interpolation):

style={{
fontSize: 0.5,
opacity: this.state.fade,
fontWeight: '400',
textAlign: 'center',
textAlignVertical: 'center',
transform: [
{translateY: this.state.slide}
]
}}

Now, we can put an Animated.sequence in the lifecycle with two separate animations:

componentDidMount() {
//start sequence
Animated.sequence([
//animation 1
Animated.timing(
this.state.fade,
{
toValue: 1,
duration: 3000,
easing: Easing.ease
}
),
//animation 2
Animated.timing(
this.state.slide,
{
toValue: 0,
duration: 3000,
easing: Easing.ease
}
)
]).start();
}

Note that we have Animated.sequence([…]).start() being wrapped around two timing animations separated by a comma. We do not need to add a .start() after each timing animation. Each animation in the sequences will start when the previous one completes.

If you test this out, you will see the title completely fade in and then slide down:

Delay

If we have a timing animation, we can add delays like this:

Animated.timing(
this.state.slide,
{
toValue: 0,
duration: 3000,
delay: 1000,
easing: Easing.ease
}
)

We have already seen this so there is no need to test it out again. However, there is another way to delay animations. This method can be applied to spring and decay animations as well.

We simply can do Animated.delay(//insert delay in ms) which can be place withing a sequence.

Let’s test this out by inserting a 3-second delay into our sequence:

componentDidMount() {
//start sequence
Animated.sequence([
//animation 1
Animated.timing(
this.state.fade,
{
toValue: 1,
duration: 2000,
easing: Easing.ease
}
),
//3-second delay
Animated.delay(300),
//animation 2
Animated.timing(
this.state.slide,
{
toValue: 0,
duration: 1000,
easing: Easing.ease
}
)
]).start();
}

If you test this out, you will notice there is now a delay between the first and second animation.

Note: This adds a delay to the default position on the animation timeline which is always to run after the previous animation.

Parallel

Another way to control animation timing is to use Animated.parallel.

This goes through an array of animations and fires them at the same time.

One way to use this is on its own.

The syntax is just like a sequence so let’s just change our current Animated.sequence to Animated.parallel as well as remove the delay we just added:

Animated.parallel([...]).start(); //remove delay

This will fire our animations at the same time.

Wait a second! How did it decide on a duration?

By default, it used the duration from our first animation (2 seconds) when it fired both animations simultaneously.

Another way to use parallel is withing a sequence. We could use this to fire our first animation and then fire two animations simultaneously after the first one completes. Here’s a visual:

Since we only have 2 properties that are being animated, the first animation will take the opacity from 1 to 0, the second animation will take the opacity from 0 to 1, and the third animation slides the title down.

First, update the state:

this.state = { fade: new Animated.Value(1), slide: new Animated.Value(1.5) };

Then, update the code for our lifecycle:

componentDidMount() {
Animated.sequence([
//animation 1
Animated.timing(
this.state.fade,
{
toValue: 0,
duration: 2000,
easing: Easing.ease
}
),
//fire animation 2 and 3 at same time
Animated.parallel([
//animation 2
Animated.timing(
this.state.fade,
{
toValue: 1,
duration: 2000,
easing: Easing.ease
}
),
//animation 3
Animated.timing(
this.state.slide,
{
toValue: 0,
duration: 2000,
easing: Easing.ease
}
)
])
]).start();
}

If we test this out, we will see the following:

Stagger

The final option for controlling animation timing is to use stagger.

stagger goes through an array of animations and runs all of them with a single delay between each one.

Like parallel, we can run this in a sequence or on its own.

To make things easy, let’s just tweak our previous lifecycle and replace Animated.parallel with Animated.stagger . The only other thing to add is a successive delay in milliseconds before the array. Let’s add a 0.5-second delay:

componentDidMount() {
Animated.sequence([
//animation 1
Animated.timing(
this.state.fade,
{
toValue: 0,
duration: 2000,
easing: Easing.ease
}
),
//fire animation 2 and 3 with success delay
Animated.stagger(500, [
//animation 2
Animated.timing(
this.state.fade,
{
toValue: 1,
duration: 2000,
easing: Easing.ease
}
),
//animation 3
Animated.timing(
this.state.slide,
{
toValue: 0,
duration: 1000,
easing: Easing.ease
}
)
])
]).start();
}

We should expect the title to fade out, begin to fade in, and then slide down 0.5 seconds into the fade in.

Let’s make sure this is working:

Awesome!

Updating Values

Another thing to cover in this section is how to set a new Animated.value.

This is really simple. Instead of doing this.setState, there’s an optimized way to update an Animated.value. For example, let’s change both values in the local state to 0:

this.state = { fade: new Animated.Value(0), slide: new Animated.Value(0) };

Now, let’s set these to their former values at the top of our lifecycle:

componentDidMount() {
this.state.fade.setValue(1);
this.state.slide.setValue(1.5);
//sequence here
}

If you test this out, you will see that our animation still works fine.

This means that the values were updated correctly.

This may have some use as you work more with animations.

Final Code

Available via Github.

Concluding Thoughts

The central aim of this chapter was to introduce the basics of animations in React VR. Animations are a big topic. I could probably write an entire book just on animations in React VR. However, I think the explanations provided are good enough for you to grasp the core concepts. Later on, we will be applying our knowledge of animations into a real-world project.

Chapter 5

Chapter 5 is now available.

Support the Author

If you would like to support me as I write this book, you can purchase the book here.

The book is published through a platform called LeanPub which allows me to update my book as it progresses. Each time a chapter is added, you will be notified via email. The book will be free to read on Medium, however, purchasing it through LeanPub allows you to download the ebook as a PDF, EPUB, or MOBI file and helps support me financially.

Additionally, I have created a special package that will provide you with a secret link to a Discord server where you will be able to help influence how I write this book.

So go ahead and purchase this book on LeanPub if you would be so kind.


Cheers,
Mike Mangialardi
Founder of Coding Artist