How to configure and build 3D Models in React application using REACT-THREE-FIBER

Dominika Niziołek
5 min readNov 17, 2021

--

ThreeJS is an awesome tool that I’ve recently discovered — probably a little later than most of the developers that try to be up-to-date, but nevertheless I’ve fallen in love with it immediately. It has artsy vibe and gives you so many possibilities to build really creative and breathtaking projects. By coincidence, around that time when I was going through every possible tutorial online, I’ve been contacted by Woman in Technology branch in my company and asked to prepare some interesting workshop for employees, preferably in React. Immediately I’ve thought — react-three-fiber. I’ve decided to prepare something about the basics, for the newbies that knows and likes React, but maybe this kind of cool library would make them more crazy about this topic. This article is not only some kind of cheat sheet for the workshop participants, but hopefully useful tutorial for developers who don’t know react-three-fiber yet, but they are willing to give it a try.

Here’s a sneak peak what you’ll learn in this article:

ThreeJS is cross-browser JavaScript library used to create and display animated 3d computer graphics in a web browser using WebGL. But who would want to write in VanillaJS? Obviously, we are all using only React for a couple of years. Fortunately, we don’t need to go to the basics and we can use react-three-fiber, awesome react renderer for ThreeJS.

1. Setup

After creating react app, let’s install some important dependencies:

npm i three @react-three/fiber @react-three/drei @react-three/cannon

We need three, obviously, and react-three-fiber. Drei and cannon are some really amazing helpers, that would make our first experience with three even more fun.

2. Plane

Firstly, in our little empty world, let’s create a function called Scene, which will return Canvas component.


const Scene = () => {
return (
<Canvas>
</Canvas>
)
}
const App =() => {
return (
<Scene/>
)
}

Canvas is the most important object in our code — it defines our react-three-fiber scene. And that means, that everything inside the Canvas — any component we’ll put in there — will not be rendered by react-dom anymore. Instead, it will be rendered by react-three-fiber and will be a component inside a 3d environment.

Now let’s put some objects inside our Canvas.

const Scene = () => {
return (
<Canvas>
<mesh>
<planeGeometry args={[100, 100]}/>
</mesh>
</Canvas>
)
}

PlaneGeometry comes from three.js, so no import is needed. The arguments it takes — two values in the array — are width and height of the plane.

For the future use — let’s extract that plane into separate function.

const Plane = () => {
return (
<mesh>
<planeBufferGeometry args={[100, 100]}/>
</mesh>
)
}
const Scene = () => {
return (
<Canvas>
<mesh>
<Plane/>
</mesh>
</Canvas>
)
}

If we want to add some colour to it, we can add some material to it. Let’s go with meshStandardMaterial.

const Plane = () => {
return (
<mesh>
<planeBufferGeometry args={[100, 100]}/>
<meshStandardMaterial color='lightpink'/>
</mesh>
)
}

But… is it black?

Well, of course it is. We don’t have any light!

<pointLight position={[10,10,10]}/>

And now it looks properly.

3. Animations

Let’s dive in into animations now. What we need to do, is build an animation loop by using useFrame.

useFrame is a Fiber hook that lets you execute code on every single frame of Fiber’s render loop.

Let’s remember: we can only call Fiber hooks inside a <Canvas/> parent!

We’ll write an arrow function called, spoiler — RotatingBox.

const RotatingBox = () => {
useFrame(({clock}) => {
const a = clock.getElapsedTime();
rotatingBoxMesh.current.rotation.x = a;
});
const rotatingBoxMesh = React.useRef();
return (
<mesh ref={rotatingBoxMesh}>
<boxGeometry/>
<meshPhongMaterial color='purple'/>
</mesh>
}

Soooo… what happened here?

The callback we pass to useFrame will be executed every frame and it will be passing an object containing the state of our Fiber scene. We want to directly mutate our mesh each frame — so firstly, we are getting a reference for it, so we could use useRef React hook.

So rotatingBoxMesh variable now hold a reference to the actual three.js object, that we can mutate in useFrame.

Let’s take a closer look into this fragment of code:

useFrame(({clock}) => {
const a = clock.getElapsedTime();
rotatingBoxMesh.current.rotation.x = a;
});

We are destructing clock from the argument passed to useFrame. Then we are accessing the rotation.x property of rotatingBoxMesh.current object (which is a reference to our mesh object). Next, we are assigning value a to the rotation on x axis, meaning our object will now infinitely rotate between -1 and 1 radians around x axis.

Easy.

4. OrbitControls

Adding OrbitControls is like making a camera — we can move screen and view each side of our 3d object. For this we’ll use another React hook, useEffect.

const CameraController = () => {   const { camera, gl } = useThree();   useEffect(() => {      const controls = new OrbitControls(camera, gl.domElement);      controls.minDistance = 3;      controls.maxDistance = 200;      return () => {         controls.dispose();      };   }, [camera, gl]);   return null;};

useThree gives you a camera object, which is used to move on screen, and gl indicates the area on which you are moving. In useEffect, we combined both the OrbitControls objects and then we added the minDistance and maxDistance parameters to restrict the movement on screen.

5. Starry sky

Let’s try some drei.

<Canvas>
...
<Sky distance={450000} sunPosition={[-100, -100, 100]} inclination={0} azimuth={0.25} />
<Stars radius={100} depth={50} count={5000} factor={4} saturation={0} fade speed={1} />
</Canvas>

6. Physics

Our small world is really pretty now, but it lacks the most entertaining thing — Physics. Let’s import it.

import {Physics} from "@react-three/cannon";

So the components we want to have in our Physics environment should be wrapped in Physics tag. We’ll create another box — but this one will be answering to gravity.

const FallingBox = () => {   const [fallingBoxMesh, api] = useBox(() => ({mass: 1, position:    [2, 2, 0]}));   return (      <mesh ref={fallingBoxMesh} position={[2, 2, 0]}      onClick={() => {api.velocity.set(-5, -2, 0);}}>         <boxGeometry />         <meshStandardMaterial color="blue" />      </mesh>   );};

Now we need to get a ref inside our FallingBox component to the Physics box representation. We are using imported method useBox, and in the callback we can add some properties — like the mass of the box. Now the gravity — and other objects inside physics world — can actually affect it!

But the box falls through the plane now — we need to add plane to our cannon hook.

const Plane = () => {
const [planeRef] = usePlane(() => ({rotation: [-Math.PI / 2, 0, 0]]}));
return (
<mesh ref={planeRef} position={[0, 0, 0]} rotation={[-Math.PI / 2, 0, 0]}>
...
</mesh>
)
}

7. Let’s move some stuff around

Cannon gives you one more really cool thing — ability to interact with created element. Using an api, we can apply onClick positions, rotations, velocities, forces and impulses.

Above you can find only the basic components. Their knowledge will be a good start to creating some really advanced, impressive webpages, in-browser games and other projects.

Below you can find repository with the code to play with 👩🏼‍💻.

EDIT 2023: In the repository there is a new, updated version that takes into consideration newest releases of all the used libraries. The code changes however are pretty minor; planeBufferGeometry is replaced simply by planeGeometry and there is a little different configuration of props for <Sky /> and <Stars /> coponents. The mentions in the article are changed appropriately.

--

--

Dominika Niziołek

Lead Front-End Developer passionate about web animations.