codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Follow publication

React 16 + Three.js integration tips (2019)

In our previous article, we’ve already implemented the most basic React and Three.js integration, which resulted in a green cube that slowly rotated on the screen. In this post, we’ll improve the app by adding the most common features.

Marina Vorontsova
codeburst
Published in
6 min readMay 13, 2019

Author: Alexander Solovyev, a web developer at Soshace.com

Hereinbelow, we’ll try to make our app:
- responsive,
- interactive with the addition of camera controls;

… as well as:

- set scene size via container dimensions instead of full window size,
- unmount without errors or memory leaks.

However, before we start improvements, let’s work a bit on a code structure and explain the code in more details.

Code structure

The goal of the first article was to start with React and Three.js with as little effort as possible; the goal, which we successfully accomplished within about 5 minutes. However, the code that was simply copy-and-pasted from Three.js tutorials is not easy to maintain, so we’ve improved the app structure by splitting the original code of componentDidMount() into a few methods with concise names:

At the moment we have 3 new methods: sceneSetup(), addCustomSceneObjects(), and startAnimationLoop(), while everything else is left untouched. Let’s move on and see what they are about.

Scene Setup

Three.js scene setup is a pretty standard task that can be seen in different examples with minor changes in config. The aim of this method is to take care of the scene, camera, and renderer creation. If you are not familiar with those terms, please check Three.js “Creating a scene” manual.

You might have noticed that now we don’t use window.innerWidth or window.innerHeight to set scene size and camera aspect ratio as in the previous article: we use container dimensions instead. That might be helpful in case your visualization takes only a part of the screen. Anyway, it is up to you to set up width and height back to fullwindow size.

Next modification of Three.js example code is using class properties for all objects that should be accessible across the Component and const for local variables instead of using var in all cases. That helps us to avoid global scope pollution.

Custom Scene Objects

Next method is used for adding any custom Three.js objects into a scene, so it’s a place for experiments. For example, I have taken the code below from Three.js example for BoxGeometry without any modifications except for using constinstead of var. This way, any Three.js example might be easily adopted as follows:

The only object that I didn’t make local is the cube because we might want to animate it later.

Animation Loop

The last method at the current stage is for scene animation:

In this method, I left comments on cube rotation to highlight the fact that it is just a visual effect and can be skipped if you don’t need it. Two next lines are essentially the animation loop. Here’s the quote from the Three.js docs:

This will create a loop that causes the renderer to draw the scene every time the screen is refreshed (on a typical screen this means 60 times per second). If you’re new to writing games in the browser, you might say “why don’t we just create a setInterval ?” The thing is — we could, but requestAnimationFrame has a number of advantages. Perhaps the most important one is that it pauses when the user navigates to another browser tab, hence not wasting their precious processing power and battery life. ~ Three.js docs

One more thing: the refresh rate of the screen should not be confused with frames per second (FPS): having FPS equal screen refresh rate is desirable; lower FPS looks like freezing; and greater FPS is a waste of resources. Many of the Three.js demos come with a handy Stats utility that displays FPS count and might be very useful for Three.js performance tuning.

Camera Controls

Since we’re done with the methods and have explained all the necessary code, let’s move on and add interactivity to the app by importing and instantiating of camera controls that would allow a user to rotate and move the camera around the object in a scene. For camera controls, there is a Three.js utility named OrbitControls that works with Perspective and Orthographic camera types. The main issue with this utility was that it could only work using a global THREE object so that it wasn’t easy to import and use in React app.

Thank to the Three.js maintainers, now we can finally use ES6 OrbitControls and import it from the three npm package in 2019 without any side packages:

OrbitControls are not located in a utility folder but rather — under examples, just as a lot of other helpful stuff that Three.js maintainers don’t call utils, for example, OBJLoader which is necessary for loading of .OBJ models into a scene.

After the import of OrbitControls we should instantiate it in asceneSetup() method after a camera is available. For example, that might be the next line after acamera instance is created:

this.el should be passed as a second param into an OrbitControls constructor only for event listeners bindings and is optional (in case your scene is full-screen in size, for example). However, if the scene is only a part of the screen, then you probably would like to disable zoom on scroll, otherwise scrolling a page would also cause zooming of the scene.

Now the user can interact with a scene with a mouse: rotate and move it in any direction.

To learn more about camera controls, please, check Three.js OrbitControls manual.

Responsiveness

Next thing to do is to make our app responsive because almost every app needs to be responsive nowadays, so we should add an event listener at the end of the componentDidMount() method:

In the code above, we’ve updated camera aspect ratio and renderer size to match current container dimensions. After making changes to most of the cameraproperties you have to call .updateProjectionMatrix() for the changes to take effect. That is the Three.js optimization: you can group multiple camerachanges into a block with only one .updateProjectionMatrix() call.

Now, the scene adapts to the window size and the app can be used on mobile.

If you have a more complicated use case of scene resizing, like scaling or preserving constant height in full-screen mode on window resize, you should check Three.js FAQ as soon as they have a special formula for field-of-view calculation with a live demo.

Unmount

According to the React guide for Other Libraries integration, we should take care of clean up in acomponentWillUnmount() lifecycle method. Basically, we should remove everything that was attached to the window object, such as:
- any custom event listeners (resize in our case),
- requestAnimationFrame()
- any Three.js event listeners, OrbitControls utility in our case. It has some event listeners attached to the window even if this.el is passed into a camera controls constructor that might be cleared using a dispose method according to their docs.

Having all that in mind, let’s write some code:

While removing event listener is a standard practice, cancelAnimationFrame()method probably needs a bit of explanation: it’s a method paired with requestAnimationFrame() and uses requestID to cancel next scheduled frame request.

We are done for now: we now have an interactive and responsive React app with Three.js inside that fits container size and might be unmounted without errors or memory leaks.

Live Demo

Live demo besides the code in the article also has a wrapper to showcase mount/unmount behavior, some basic styling and a hint on how to use a mouse for a scene controlling.

The author of this tutorial is Alexander Solovyev, a web developer currently employed at Soshace.com, a hiring platform for web developers: find a developer or apply for a remote job.

We’ll post more tutorials and articles from people working at Soshace. Stay tuned.

Published in codeburst

Bursts of code to power through your day. Web Development articles, tutorials, and news.

Written by Marina Vorontsova

I am a copywriter: I like reading and writing stories, above-average copy, and delightfully inferior poetry.

Responses (2)

Write a response