One interesting way to stand out and attract your user’s attention is to use 3D elements in your web experiences. In this post, we’re going to see how we can create interactive 3D icons using React and React Three Fiber (R3F) with TypeScript. If you don’t want to use TS that’s alright, just remove the types whenever needed.
At the end of this post, you can find the link for the complete code.
Dependencies
To get started, install the following dependencies in your React app: three: The core Three.js library, which allows us to work with 3D objects in the browser. react-three/fiber: A React renderer for Three.js. @react-three/drei: A helpful toolkit for react-three/fiber.
To install them, type in your terminal
npm install three @react-three/fiber @react-three/drei
Creating the 3D Icons
Setting up the Canvas and Lighting
Next, we need to wrap our 3D elements in the Canvas component from @react-three/fiber:
import { Canvas } from "@react-three/fiber";
function App() {
return (
<div className="App">
<Canvas
style={{ width: "100vw", height: "90vh" }} //to make our canvas take almost the whole screen
camera={{ position: [0, 0, 15] }} //’zoom out’ of 15 meters
>
<directionalLight position={[0, 1, 2]} intensity={5} color="white" />
<ambientLight intensity={0.6} />
{icons.map((icon, index) => {
const gap = 5;
// prettier-ignore
const horizontalPosition = gap - (index * gap);
return (
<Icon3D
imagePath={icon}
position={[horizontalPosition, 1, 0]}
shape={shape}
/>
);
})}
</Canvas>
}
directionalLight: enhances the shading and lighting, providing depth. We can play around with the ‘position’ and ‘intensity’ to our taste.
ambientLight: provides a uniform light to the scene.
icons.map: in this code, we’re mapping through an array of icons, but if you prefer, you can add just one ‘Icon3D’ passing as the imagePath you desired icon.
Defining the Icon3D Component
This is where we’re going to put the icons and generate the shapes. Let’s see the complete code, then we visit step by step what’s happening:
import { Decal, Float, useTexture } from "@react-three/drei";
import { useFrame } from "@react-three/fiber";
import { useRef, useState } from "react";
import { Mesh } from "three";
import { white, blue } from "../../App";
const RADIUS = 2;
export function Icon3D({ imagePath, position, shape }: Icon3DProps) {
const texture = useTexture(imagePath);
return (
<mesh>
<dodecahedronGeometry args={[RADIUS, 0]} />
<meshStandardMaterial
metalness={0.6} //to add a more ‘real’ metallic feeling to the icon
roughness={0.5}
/>
<Decal
position={[0, 0, 2]}
rotation={[2 * Math.PI, 0, 6.25]}
scale={1}
map={texture}
/>
</mesh>
);
}
useTexture loads textures for our icons.
'mesh': in R3F, our 3D elements are wrapped in a mesh. The mesh needs geometry and material. Think of the mesh as the complete body, the geometry as the bones, and the material as the skin.
'dodecahedronGeometry': The specific shape of this mesh, but there are other possibilities. All geometries available in Three.js can be used in R3F. For more information, check Three.js documentation or R3F docs.
'meshStandardMaterial': The skin of our mesh, influences how the shades and lights interact with the mesh. For more information, search the documentation.
'Decal': This is a component that comes from '@react-three/drei', and allows us to add an image on a surface. We can play with the position, rotation, and scale to make it fit correctly. Then, we pass our texture to its map props.
Adding Interactivity to 3D elements
Now that we have the barebones of our 3D Icon, we can start adding extra functionality, such as user interaction.
One of the coolest things about R3F is the possibility of interacting with 3D elements as React components, so we can use React’s event-handling patterns. For instance, in case we want to add hover and click events, it’s pretty practical:
const mesh = useRef<Mesh | null>(null); //we initialize the reference to the mesh, so we can interact with it
const [hovered, setHovered] = useState(false);
const [clicked, setClicked] = useState(false);
// Inside the component's return statement
<mesh
ref={mesh} // we pass the ref to the mesh
onPointerEnter={() => setHovered(true)} // mesh recognizes other events too
onPointerLeave={() => setHovered(false)}
onClick={() => setClicked(!clicked)}
>
{/* ... */} // we can then personalize what the interactions do to the mesh
</mesh>
The meshes recognize several different events. To see all of them, check out the R3F docs on events.
Conclusion
By leveraging React and R3F, we open up a new world of possibilities for web experience. We can use the familiar characteristics of React with the power of Three.js through R3F in an intuitive way.
Hopefully, this post provided you with the foundational knowledge to get started with this powerful combination.
To explore more, you can access the complete code here.
See you next time!