React + Three.js + React Three Fiber: Crafting Interactive 3D views in Web Applications
Last Update: 16 Oct 2024

Introduction
Overview of Three.js and React
Three.js is a robust JavaScript library designed for creating and displaying 3D graphics in web browsers. It abstracts the complexities of WebGL, enabling developers to create 3D scenes with relative ease. With features like lighting, shadows, textures, and animations, Three.js opens the door to visually stunning applications, from simple 3D models to fully immersive environments.
React is a widely-used JavaScript library developed by Facebook for building user interfaces, particularly single-page applications. Its component-based architecture allows developers to create reusable UI elements, manage state efficiently, and build dynamic applications that respond quickly to user interactions. React’s declarative approach simplifies the development process, making it easier to maintain and scale applications.
Importance of Integrating Both for Modern Web Development
Integrating Three.js with React brings together the strengths of both libraries, creating a powerful toolkit for web developers. By combining React’s efficient UI management with the 3D capabilities of Three.js, developers can build applications that are not only visually captivating but also highly interactive and responsive.
This integration allows for:
- Dynamic 3D Content: Easily update and manipulate 3D objects based on user interactions or state changes.
- Component-Based Structure: Organize 3D elements as React components, making the code more maintainable and reusable.
- Enhanced User Experiences: Create engaging and interactive applications, from educational tools to gaming experiences, that stand out in today’s digital landscape.
Prerequisites
Before diving into the integration of Three.js with React, it’s helpful to have a few foundational skills and knowledge areas. While you don’t need to be an expert, a basic understanding of the following will make the learning process smoother
Basic Understanding of JavaScript:
-
- Since Three.js and React are both JavaScript libraries, a solid grasp of JavaScript fundamentals is essential. Familiarity with concepts like variables, functions, objects, and arrays will be crucial.
Familiarity with React:
A fundamental knowledge of React is important for integrating Three.js effectively. You should be comfortable with concepts such as:
-
-
- Components: Understanding how to create and use functional components.
- Props and State: Knowing how to pass data between components and manage local state.
- Lifecycle Methods and Hooks: Familiarity with hooks like
useEffect
anduseState
will help you manage the rendering and updates of your 3D scenes.
-
Familiarity with 3D Concepts (Optional, but Helpful):
-
- While not strictly necessary, having some basic knowledge of 3D graphics can enhance your understanding and make working with Three.js more intuitive. Key concepts include:
- Scenes, Cameras, and Renderers: Understanding how these components work together to create a 3D environment.
- Meshes and Materials: Knowing how 3D objects are constructed and textured.
- Lighting and Shadows: Recognizing how different light types affect the appearance of objects in a scene.
- Resources like online tutorials or introductory articles on 3D graphics can provide a helpful background.
- While not strictly necessary, having some basic knowledge of 3D graphics can enhance your understanding and make working with Three.js more intuitive. Key concepts include:
Basic Concepts of Three.js
Three.js is a powerful JavaScript library that simplifies the process of creating 3D graphics in the web browser.
Key Components
-
Scene
- The scene is the environment where all 3D objects are placed. It acts as a container for your objects, lights, and cameras.
- You can think of it as a virtual stage where everything happens.
- Creating the scene, we need three things: scene, camera and renderer, so that we can render the scene with camera.
import * as THREE from 'three'; const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); // Create renderer for scene const renderer = new THREE.WebGLRenderer(); // set renderer size as window height and width renderer.setSize( window.innerWidth, window.innerHeight ); // you can also append renderer to any html element document.body.appendChild( renderer.domElement );
-
Camera
- The camera defines the viewpoint from which the scene is rendered. There are different types of cameras, but the most common are:
- PerspectiveCamera: Mimics the human eye, with a field of view that decreases with distance.
- OrthographicCamera: Renders objects without perspective distortion, useful for 2D or isometric views.
- Creating perspective camera
const camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
- The camera defines the viewpoint from which the scene is rendered. There are different types of cameras, but the most common are:
-
Renderer
- The renderer is responsible for displaying the scene from the perspective of the camera. Three.js primarily uses WebGL for rendering, which allows for hardware-accelerated graphics.
- We create a renderer and attach it to an HTML element (usually a
<canvas>
).
-
Mesh
- A mesh is a 3D object made up of geometry (the shape) and material (the appearance).
- Geometries define the vertices and faces of the object, while materials determine how the object interacts with light (e.g., color, texture).
- Creating a Mesh with plane geometry
const geometry = new THREE.PlaneGeometry( 1, 1 ); const material = new THREE.MeshBasicMaterial( {color: 0xffff00, side: THREE.DoubleSide} ); const plane = new THREE.Mesh( geometry, material ); scene.add( plane );
Brief Introduction to Geometries, Materials, and Lights
-
Geometries
- Geometries represent the shapes of 3D objects. Common geometries include:
- BoxGeometry: A rectangular box.
- SphereGeometry: A sphere.
- PlaneGeometry: A flat surface.
- You can also create custom geometries by defining your own vertices and faces.
- Geometries represent the shapes of 3D objects. Common geometries include:
-
Materials
- Materials define how a mesh appears visually. Key types include:
- MeshBasicMaterial: A simple material that does not react to light; it’s good for basic coloring.
- MeshLambertMaterial: A material that reflects light, providing a matte appearance.
- MeshPhongMaterial: A shiny material that reacts to light and has specular highlights.
- MeshStandardMaterial: A more advanced material that supports physically-based rendering.
- Materials define how a mesh appears visually. Key types include:
-
Lights
- Lighting is crucial for creating realistic scenes. Common light types include:
- AmbientLight: Provides a uniform light that illuminates all objects equally.
- DirectionalLight: Simulates sunlight; it has a direction and casts shadows.
- PointLight: Emits light in all directions from a point; behaves like a light bulb.
- SpotLight: Emits light in a cone shape, useful for focused lighting.
- Lighting is crucial for creating realistic scenes. Common light types include:
Creating a Simple 3D Scene with Three.js and vanilla js
Step 1: Creating project with vite:
npm create vite@latest vanilla_3d_web --template vanilla
Step 2: Navigate to the Project Directory:
cd vanilla_3d_web
Step 3: Install Dependencies:
npm install
Step 4: Start the Development Server:
npm run dev
Step 5: Edit i"/index.html":
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3D web view with vanilla js + Three.js</title>
</head>
<body>
<canvas id="main"></canvas>
<script type="module" src="/main.js"></script>
</body>
</html>
Step 6: Reset canvas styles and position to window: "/styles.css"
* {
margin: 0;
padding: 0;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
Step 7: writting Three.js to render 3D view "/main.js":
import './style.css'
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/Addons.js';
// main scene
const scene = new THREE.Scene();
// main camera
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// main renderer
const renderer = new THREE.WebGLRenderer({
canvas: document.getElementById("main")
})
// set pixel ratio of renderer
renderer.setPixelRatio(window.devicePixelRatio);
// set renderer witdth and height
renderer.setSize( window.innerWidth, window.innerHeight );
// set camera position
camera.position.setZ(40);
// create opject
const geometry = new THREE.TorusGeometry(10, 3, 16, 100);
// the Standard Mesh Material react to light source
const material = new THREE.MeshStandardMaterial({ color: 0xFF6347 });
const torus = new THREE.Mesh(geometry, material);
// add torus to scene
scene.add(torus);
// add light source
const pointLight = new THREE.PointLight(0xffffff);
// set the position
pointLight.position.set(20,20,20);
// ambient light
const ambientLight = new THREE.AmbientLight(0xffffff);
// add point light to scene
scene.add(pointLight, ambientLight);
// light helper which help to locate where the light is
const lightHelper = new THREE.PointLightHelper(pointLight);
scene.add(lightHelper);
// add grid helper
// const gridHelper = new THREE.GridHelper(200, 50);
// scene.add(gridHelper);
// orbit controls that use to explore scene with mouse
const controls = new OrbitControls(camera, renderer.domElement);
// rendom star adding to scene
function addStar() {
const geometry = new THREE.SphereGeometry(0.25, 24, 24);
const material = new THREE.MeshStandardMaterial( {color: 0xfffff});
const star = new THREE.Mesh( geometry, material );
// give the star a random postion
const [x, y, z] = Array(3).fill().map(() => THREE.MathUtils.randFloatSpread(100));
star.position.set(x, y, z);
scene.add(star);
}
// add 200 random start
Array(200).fill().forEach(addStar)
// rerender like game loop function
function animate() {
requestAnimationFrame( animate );
torus.rotation.x += 0.01;
torus.rotation.y += 0.005;
torus.rotation.z += 0.01;
controls.update()
renderer.render(scene, camera);
}
animate()
Result:
Integrating Three.js with React
Setting Up Development Environment
Creating a New React App
- Option A: Using Create React App
- Install Create React App (if you haven't already): Open your terminal and run:
npx create-react-app 3d_web
- Navigate to Your Project Directory:
cd 3d_web
- Start the Development Server:
npm start
- Install Create React App (if you haven't already): Open your terminal and run:
- Option B: Using Vite
-
Install Vite (if you haven't already): Open your terminal and run:
npm create vite@latest 3d_web --template react
-
Navigate to Your Project Directory:
cd 3d_web
-
Install Dependencies:
npm install
-
Start the Development Server:
npm run dev
-
Installing Three.js
- Install Three.js and React Three Fiber: In your project directory, run the following command:
npm install three @react-three/fiber
- Additional Dependencies : GLTFLoader for importing 3D models:
npm install @react-three/drei
Creating 3D scene: "/src/App.tsx"
import { OrbitControls, useTexture } from "@react-three/drei"
import { Canvas } from "@react-three/fiber"
const App = () => {
return (
<div style={{background: "grey", height: "100vh", width: "100vw"}}>
<Canvas />
</div>
)
}
Here, we are using "Canvas" from "@react-three/fiber" that automatically setup Scenes, Cameras, and Renderers for us
Adding light and object to the scene: "/src/App.tsx"
import { OrbitControls, } from "@react-three/drei"
import { Canvas } from "@react-three/fiber"
const App = () => {
return (
<div style={{background: "grey", height: "100vh", width: "100vw"}}>
<Canvas>
{/* ambientLight for illuminates all objects equally. */}
<ambientLight intensity={0.5}/>
{/* orbitcontrols to move around with mouse */}
<OrbitControls />
<mesh position={[0, 0, 0]} rotation={[Math.PI * -0.5, 0, 0]}>
<planeGeometry args={[500, 500]} />
<meshStandardMaterial color={"#18d6b6"} />
</mesh>
{/* orbitcontrols to move around with mouse */}
<OrbitControls />
</Canvas>
</div>
)
}
Building an Interactive 3D Component
Here we are going to build simple click color change game:
Creating Ground for a scene:
import {useTexture} from "@react-three/drei"
const BreakGround = () => {
const textureMap = useTexture("./textures/ground/red_brick_diff.png");
return <mesh position={[0, 0, 0]} rotation-x={Math.PI * -0.5}>
<planeGeometry args={[500, 500]} />
<meshStandardMaterial map={textureMap} />
</mesh>
}
Creating InteractiveBall component that can be interact with click event:
import { Color, Vector3 } from "three";
const InteractiveBall = ({onClick, color, position}: {onClick: () => void, color: Color, position: Vector3}) => {
return <mesh
onClick={() => {
onClick()
}}
position={position}
>
<sphereGeometry args={[10 ,10, 10]} />
<meshStandardMaterial color={color} />
</mesh>
}
Creating helper function that can helper function to interact with ball:
// generate random reward 1000 to 100000
const getRandomNumber = () => {
return Math.abs(Math.floor(Math.random() * (1000 - 100000 + 1)) + 1000);
}
// generate random color for ball
const getRandomColor = () => {
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
return new THREE.Color(randomColor);;
}
Creating simple state screen:
<div style={{
position: "absolute",
top: "0px",
left: "0px",
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<div style={{
width: "300px",
height: "300px",
backgroundColor: "rgb(249 215 220 / 0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
rowGap: "10px"
}} >
<h1 style={{
fontSize: "30px",
color: "#effa16",
fontWeight: "900"
}}>Congrats!</h1>
<p style={{
fontSize: "50px",
color: "#84fa16",
fontWeight: "900",
textAlign: "center"
}}>You won <span style={{
color: "#f76fee"
}}>${getRandomNumber()}</span> </p>
<button style={{
padding: "8px 20px",
backgroundColor: "#fa78e4",
color: "#fff",
fontWeight: "700",
borderRadius: "0.75rem"
}}
onClick={() => {
setGameState(false)
setClickCounter(0);
}}
>Play again</button>
</div>
</div>
Putting it all togather:
import { OrbitControls, useTexture } from "@react-three/drei"
import { Canvas } from "@react-three/fiber"
import { useEffect, useState } from "react";
import { Color, Vector3 } from "three";
import * as THREE from "three";
const BreakGround = () => {
const textureMap = useTexture("./textures/ground/red_brick_diff.png");
return <mesh position={[0, 0, 0]} rotation-x={Math.PI * -0.5}>
<planeGeometry args={[500, 500]} />
<meshStandardMaterial map={textureMap} />
</mesh>
}
const InteractiveBall = ({onClick, color, position}: {onClick: () => void, color: Color, position: Vector3}) => {
return <mesh
onClick={() => {
onClick()
}}
position={position}
>
<sphereGeometry args={[10 ,10, 10]} />
<meshStandardMaterial color={color} />
</mesh>
}
const getRandomNumber = () => {
return Math.abs(Math.floor(Math.random() * (1000 - 100000 + 1)) + 1000);
}
const getRandomColor = () => {
const randomColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
return new THREE.Color(randomColor);;
}
const App = () => {
const [ball_1_Color, setBall_1_Color] = useState<Color>(getRandomColor);
const [ball_2_color, setBall_2_color] = useState<Color>(getRandomColor);
const [gameState, setGameState] = useState<boolean>(false);
const [clickCounter, setClickCounter] = useState<number>(0);
useEffect(() => {
if (ball_1_Color.equals(ball_2_color)) {
setGameState(true);
}
if (clickCounter === 10) {
setBall_1_Color(ball_1_Color);
setBall_2_color(ball_2_color);
setGameState(true);
}
}, [ball_1_Color, ball_2_color, clickCounter])
return (
<div style={{
position: "relative"
}}>
<div style={{background: "grey", height: "100vh", width: "100vw", position: "fixed", left: "0px", top: "0px"}}>
<Canvas camera={{fov: 75, position: [10, 35, 50]}}>
<ambientLight intensity={0.5}/>
<spotLight castShadow position={[0, 2, 0]} />
<directionalLight position={[0, 10, 10]} />
{/* orbitcontrols to move around with mouse */}
<OrbitControls />
<BreakGround />
<InteractiveBall onClick={() => {
setClickCounter((state) => state += 1)
setBall_1_Color(getRandomColor())
}} color={ball_1_Color} position={new Vector3(0, 10, 0)} />
<InteractiveBall onClick={() => {
setBall_2_color(getRandomColor())
setClickCounter((state) => state += 1)
}}
color={ball_2_color}
position={new Vector3(20, 10, 0)}
/>
</Canvas>
</div>
{/* win screen */}
{
gameState &&
<div style={{
position: "absolute",
top: "0px",
left: "0px",
width: "100vw",
height: "100vh",
display: "flex",
alignItems: "center",
justifyContent: "center"
}}>
<div style={{
width: "300px",
height: "300px",
backgroundColor: "rgb(249 215 220 / 0.5)",
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
rowGap: "10px"
}} >
<h1 style={{
fontSize: "30px",
color: "#effa16",
fontWeight: "900"
}}>Congrats!</h1>
<p style={{
fontSize: "50px",
color: "#84fa16",
fontWeight: "900",
textAlign: "center"
}}>You won <span style={{
color: "#f76fee"
}}>${getRandomNumber()}</span> </p>
<button style={{
padding: "8px 20px",
backgroundColor: "#fa78e4",
color: "#fff",
fontWeight: "700",
borderRadius: "0.75rem"
}}
onClick={() => {
setGameState(false)
setClickCounter(0);
}}
>Play again</button>
</div>
</div>
}
</div>
)
}
Frequently Asked Questions
Trendingblogs
Get the best of our content straight to your inbox!
By submitting, you agree to our privacy policy.