Playing with React VR
The React concept of "Learn once, write anywhere" is virtually real, I mean, it is real!
After attending a local React meetup here in Jacksonville, I decided to try React VR. I've been doing React Native for almost two years now, and almost the same APIs are available in React VR, so the learning curve for me was super low.
So if you've been doing React or React Native for the past months, you'll see that React VR is super simple to get started and will let you build exciting 360 experiences.
To get started with React VR you have to install the CLI:
npm install -g react-vr-cli
After that let's create a new project:
react-vr init HashrocketVR
Now, let's go to the created directory and start the server:
cd HashrocketVR
npm start
Now open your browser:
open http://localhost:8081/vr/
You should see this page:
Open scene in new tab for full 360 experience
Awesome. Move your mouse, and you will be able to rotate the scene. You should also try and open that url from your phone. React VR can access the accelerometer and gyroscope sensors from your phone and change the scene around.
Now let's try to understand what is happening inside the index.vr.js
file that was generated:
import {
AppRegistry,
asset,
Pano,
Text,
View,
} from 'react-vr';
export default class HashrocketVR extends React.Component {
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<Text
style={{
backgroundColor: '#777879',
fontSize: 0.8,
fontWeight: '400',
layoutOrigin: [0.5, 0.5],
paddingLeft: 0.2,
paddingRight: 0.2,
textAlign: 'center',
textAlignVertical: 'center',
transform: [{translate: [0, 0, -3]}],
}}>
hello
</Text>
</View>
);
}
};
AppRegistry.registerComponent('HashrocketVR', () => HashrocketVR);
If you are familiar with React Native, you recognize the Text
and View
components.
The Pano
component is responsible for displaying a panoramic image inside the scene. This image needs to be in a particular format called Equirectangular so that component can render a 360 image perfectly.
React VR uses the OpenGL coordinate system. The coordinate (x: 0, y: 0, z: 0) is the viewer's camera in the world. Axis X is horizontal, Y is vertical, and Z is depth. The unit dimension is in meters, not pixels.
To change the position of the objects within the scene, you can apply some transformations.
If you look at the style for the Text
component, it is setting thetransform
property to {translate: [0, 0, -3]
. It's positioning that component to be 3 meters away from the viewer.
Great. Let's do some modifications here now. How about we display a 3D Hashrocket logo instead of the Hello text.
React VR comes with a Model
component where you can use any 3D model in the obj
format.
(We don't have our logo in that format, but we have in SVG. I used this site svg2stl.com to generate a .stl
file, import it on Blender and then export it to the obj
format.)
You can put the .obj
file inside the static_assets
folder and render the Model
component like this:
import {
...
Model,
} from 'react-vr';
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<Model
source={{
obj: asset('hashrocket.obj')
}}
style={{
color: '#af1e23',
transform: [
{translate: [0, -3, -12]}
]
}}
/>
</View>
);
}
Notice that we had to apply a transformation on the Model
component in order to position it in front of the viewer. We also specified the color to paint the 3D model correctly.
This is how it should look like:
Open scene in new tab for full 360 experience
How about we add some animation to make the logo rotate in the Y axis. React VR uses the same Animated API used in React Native. First thing we have to do is to create an animated component based on Model
:
import {
...
Animated
} from 'react-vr';
const AnimatedModel = Animated.createAnimatedComponent(Model);
We have to do that because the animated API only ships with animated versions of primitive components like Text, View and Image.
Now we just replace the Model component with the AnimatedModel:
render() {
return (
...
<AnimatedModel
source={{
obj: asset('hashrocket.obj')
}}
style={{
color: '#af1e23',
transform: [
{translate: [0, -3, -12]},
]
}}
/>
...
);
}
Now we need to use the Animated API to perform the animation. First thing we do is to keep an instance of Animated.Value
in our state so it can be used in the AnimatedModel
style to set the rotateY
transformation:
export default class HashrocketVR extends React.Component {
state = {
rotation: new Animated.Value(0)
}
render() {
return (
...
<AnimatedModel
style={{
transform: [
{translate: [0, -3, -12]},
{rotateY: this.state.rotation}
]
}}
...
);
}
};
Now we create a function that will use the timing
animation to do a 360 rotation in 10 seconds:
export default class HashrocketVR extends React.Component {
...
rotate = () => {
this.state.rotation.setValue(0);
Animated.timing(
this.state.rotation,
{
toValue: 360,
duration: 10000,
}
).start(this.rotate);
}
...
};
Notice that we are passing the same function as the first argument of the start
function. This will make it recursive, and the animation will never stop.
Now we can call that function on componentDidMount
so it will be triggered right when the component is rendered on the screen:
export default class HashrocketVR extends React.Component {
...
componentDidMount() {
this.rotate();
}
...
}
This is how the whole component should look like:
export default class HashrocketVR extends React.Component {
state = {
rotation: new Animated.Value(0)
}
componentDidMount() {
this.rotate();
}
rotate = () => {
this.state.rotation.setValue(0);
Animated.timing(
this.state.rotation,
{
toValue: 360,
duration: 10000,
}
).start(this.rotate);
}
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<AnimatedModel
source={{
obj: asset('hashrocket.obj')
}}
style={{
color: '#af1e23',
transform: [
{translate: [0, -3, -12]},
{rotateY: this.state.rotation}
]
}}
/>
</View>
);
}
};
And this is the actual component in React VR:
Open scene in new tab for full 360 experience
If you pay attention to the animation right before the logo is finishing a 360 rotation you can notice that it reduces its speed. By default, the timing animation uses EaseIn/Out. You can disable that by setting a lineareasing
property on the animation. You will also have to import Easing
from react-native
:
import {
Easing
} from 'react-native';
rotate = () => {
this.state.rotation.setValue(0);
Animated.timing(
this.state.rotation,
{
toValue: 360,
duration: 10000,
easing: Easing.linear,
}
).start(this.rotate);
}
Great. Now that we have our logo rotating you can see that something is missing. In the real world, light plays an important role, and it changes the appearance of any objects. In a 3D virtualized world is the same thing. To have a more realistic UI, we need to add lights to our scene. React VR provides 4 different components for lights: AmbientLight, DirectionalLight, PointLight and SpotLight.
Let's change our component and add AmbientLight and PointLight:
import {
...
AmbientLight,
PointLight,
} from 'react-vr';
render() {
return (
<View>
<Pano source={asset('chess-world.jpg')}/>
<AmbientLight intensity={0.5} />
<PointLight
style={{
color:'white', transform:[{translate:[0, 0, 0]}]
}}
/>
<AnimatedModel
lit
source={{
obj: asset('hashrocket.obj'),
}}
style={{
color: '#af1e23',
transform: [
{translate: [0, -3, -12]},
{rotateY: this.state.rotation}
]
}}
/>
</View>
);
}
Notice that we also had to set the prop lit
on the AnimatedModel
. That will enable that component to receive lights.
This is how the final scene looks like with the lights:
Open scene in new tab for full 360 experience
Wow!!! The lights changed the logo, and now you can clearly see the edges of it. That is awesome!!!
Done. Our 3D scene is complete. But before we finish you should know that React VR can bundle all your js files so you can upload to a static page or embed as an iframe (same as I did here with the examples). Just run this in the command line:
npm run bundle
The files will be generated inside vr/build
.
That is it for this blog post. The source code for the whole example app is available here. Hope you got inspired to try React VR and create some awesome 360 experiences.
React VR also comes with other interesting components that I haven't played with like VrButton, Sound, VideoPano. Make sure to read the docs.
React is a great library. When you learn React you can write apps for various platforms like Web, iOS, Android, Apple TV, Windows and now VR. The future for React is brilliant, can't wait to see what platform is next.