This tutorial is out of date and no longer maintained.
react-three-fiber
(often shortened to R3F
) allows you to use three.js
in your React applications to create and display 3D computer graphics for web browsers.
If you’re familiar with working with React and you want to create a three.js
experience, the standard approach can be painful to scale. react-three-fiber
addresses this pain point, by providing three.js
conventions that work within the React ecosystem.
Some examples of projects created with react-three-fiber
include Let Girls Dream, Gucci’s 1955 Horse Bit Bag and Gucci’s 24 Hour Ace.
In this article, you will learn how to add 3D graphics and animations to your React apps using three.js
by leveraging the react-three-fiber
library.
This article supposes you are familiar with React (with hooks)
and three.js
. If not, you can get a quick start on React hooks here and on three.js here.
Note: A live version of this project is available on CodeSandbox. For the purposes of this tutorial, there will be an emphasis on code splitting in multiple files to better illustrate working with components. It is important to note that this approach is not performance-optimized.
This tutorial assumes you have a React app with three
, react-three-fiber
, as lodash
as dependencies.
react-three-fiber
Here are some reasons to consider react-three-fiber
for your next project:
three.js
objects in a declarative way, so you can build up your scene by creating re-usable React components, leveraging props
, states
and hooks
. Keep in mind that you can write essentially the entire three.js
object catalogue and all its properties.raycaster
and it gives you access on each mesh
to all the useful pointer-events like onClick
, onPointerOver
, onPointerOut
, and more. Exactly like any DOM element.useFrame
that allows us to attach functions into the raf
loop (or even override the default one), and useThree
from where we can get useful objects like renderer
, scene
, camera
, and more.size
of the viewport
(the size of the quad you are rendering based on 3D coordinates).three.js
version, so you are free to choose your preferred version.Next, let’s use react-three-fiber
.
First, you will need to define the Canvas
component. Everything inside of it will be added to the main scene
(defined by react-three-fiber
).
Open the /src/App.js
file in your code editor. Replace the code with the following new lines:
import { Canvas } from "react-three-fiber";
function App() {
return (
<div className="App">
<Canvas>
<!--
<Children/> // any three.js object (mesh, group, etc.)
<Children/> // any three.js object (mesh, group, etc.)
-->
</Canvas>
</div>
);
}
export default App;
The first step is done. With these few lines of code, you’ve already created the canvas
, the camera
(a perspective one but you can customize it), and the scene
.
mesh
Let’s compare the two approaches on how to create a basic mesh and add it into a group.
Here is an example of defining a mesh and adding it to a group with JavaScript:
const group = new Group();
const geo = new BoxBufferGeometry(2,2,2);
const mat = new MeshStandardMaterial({color: 0x1fbeca});
const mesh = new Mesh(geo, mat);
group.position.set(0,0.1,0.1);
group.add(mesh);
scene.add(group);
And here is the same mesh using a declarative approach:
<group position={[0,0.1,0.1]}>
<mesh>
<boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
<meshStandardMaterial attach="material" color={0xf95b3c} />
</mesh>
</group>
In the declarative approach, it is not necessary to add the scene
because it’s automatically added since it’s a child of the Canvas
.
You just need to pass the required arguments as args
property and then you can set all the other properties as props.
Suppose now that you want to create multiple cubes and add them to the group.
In plain vanilla JavaScript, you would need to create a class
(or not, depends on your preferred approach) to create and handle them, and push them into an array, and so on.
With R3F
you can add an array of components into what the function returns, as any DOM-element. You can even pass a prop and apply it internally.
export default () => {
const nodesCubes = map(new Array(30), (el, i) => {
return <Cube key={i} prop1={..} prop2={...}/>;
});
return (
<group>
{ nodesCubes }
</group>
);
};
Now let’s animate them. react-three-fiber
provides you a great way to attach your logic into the raf
loop, using the useFrame
hook.
// ...
import {useFrame} from 'react-three-fiber'
// ...
const mesh = useRef()
useFrame( ({gl,scene,camera....}) => {
mesh.current.rotation.y += 0.1
})
Now let’s change some properties on hover
or on click
. You don’t need to set any raycaster
because it’s already defined for you. Since you are using React you can leverage the useState
hook.
//...
const mesh = useRef()
const [isHovered, setIsHovered] = useState(false);
const color = isHovered ? 0xe5d54d : 0xf95b3c;
const onHover = useCallback((e, value) => {
e.stopPropagation(); // stop it at the first intersection
setIsHovered(value);
}, [setIsHovered]);
//...
<mesh
ref={mesh}
position={position}
onPointerOver={e => onHover(e, true)}
onPointerOut={e => onHover(e, false)}
>
<boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
<meshStandardMaterial color={color} attach="material" />
</mesh>
Now let’s change the color and the animation by modifying the state of the cube from “active” to “inactive” based on the user’s click. As before, you can play with state
and use the built-in function onClick
.
//...
const [isHovered, setIsHovered] = useState(false);
const [isActive, setIsActive] = useState(false);
const color = isHovered ? 0xe5d54d : (isActive ? 0xf7e7e5 : 0xf95b3c);
const onHover = useCallback((e, value) => {
e.stopPropagation();
setIsHovered(value);
}, [setIsHovered]);
const onClick = useCallback(
e => {
e.stopPropagation();
setIsActive(v => !v);
},
[setIsActive]
);
// raf loop
useFrame(() => {
mesh.current.rotation.y += 0.01 * timeMod;
if (isActiveRef.current) { // a ref is needed because useFrame creates a "closure" on the state
time.current += 0.03;
mesh.current.position.y = position[1] + Math.sin(time.current) * 0.4;
}
});
//...
return (
<mesh
ref={mesh}
position={position}
onClick={e => onClick(e)}
onPointerOver={e => onHover(e, true)}
onPointerOut={e => onHover(e, false)}
>
<boxBufferGeometry attach="geometry" args={[0.047, 0.5, 0.29]} />
<meshStandardMaterial color={color} attach="material" />
</mesh>
);
ambientlight
and pointLight
As before, if you want to add some lights you will need to create a function (or component) and add it into the scene
graph:
return (
<>
<ambientLight intensity={0.9} />
<pointLight intensity={1.12} position={[0, 0, 0]} />
</>
)
rotation
Finally, let’s add custom logic into the render-loop.
Let’s add a rotation
on the container group of all the cubes. You can just simply go to the group
component, use the useFrame
hook and set the rotation there, just like before.
useFrame(() => {
group.current.rotation.y += 0.005;
});
Since I’ve discovered react-three-fiber
, I’ve used it for all my React three.js projects, from the simplest demo to the most complex one. I strongly suggest you read all the documentation online and overview all the features because here we just covered some of them.
Since R3F
is brought to you by Paul Henschel it’s totally compatible with react-spring. This means you can use react-spring
to animate all your three.js
stuff, accessing directly to the element, and to be honest it’s totally mind-blowing.
By the way, if you are used to working with three.js, you’ll have to switch a little how you interact and create stuff. It has a learning curve and you’ll need some time to change the approach if you want to keep performance at its best, like for example using a shared material, cloning a mesh, etc, but based on my experience it’s absolutely worth the time invested.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
This comment has been deleted