Three.js is a powerful JavaScript library for creating 3D graphics in the browser. When combined with React, you can build interactive 3D experiences that integrate seamlessly with your application’s UI.
1. Basic Three.js Setup in React
Using react-three-fiber (Recommended)
import { Canvas } from '@react-three/fiber';
import { OrbitControls } from '@react-three/drei';
function Scene() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
<pointLight position={[-10, -10, -10]} />
<mesh>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial color="hotpink" />
</mesh>
<OrbitControls />
</Canvas>
);
}
Vanilla Three.js Integration
import { useEffect, useRef } from 'react';
import * as THREE from 'three';
function ThreeScene() {
const mountRef = useRef(null);
useEffect(() => {
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
mountRef.current.appendChild(renderer.domElement);
// Add objects
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
camera.position.z = 5;
// Animation loop
const animate = () => {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
// Cleanup
return () => {
mountRef.current.removeChild(renderer.domElement);
};
}, []);
return <div ref={mountRef} />;
}
2. Loading 3D Models
Using GLTF/GLB Models
import { useLoader } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { Suspense } from 'react';
function Model({ url }) {
const gltf = useLoader(GLTFLoader, url);
return <primitive object={gltf.scene} />;
}
function ModelViewer() {
return (
<Canvas>
<ambientLight intensity={0.5} />
<Suspense fallback={null}>
<Model url="/model.glb" />
</Suspense>
<OrbitControls />
</Canvas>
);
}
With Progress Indicator
import { Progress } from '@react-three/drei';
function ModelWithLoader() {
return (
<Canvas>
<Suspense fallback={<Progress />}>
<Model url="/complex-model.glb" />
</Suspense>
</Canvas>
);
}
3. Interactive 3D Elements
Clickable Objects
function InteractiveBox() {
const meshRef = useRef();
const [hovered, setHover] = useState(false);
const [active, setActive] = useState(false);
useFrame((state, delta) => {
meshRef.current.rotation.x += delta;
});
return (
<mesh
ref={meshRef}
scale={active ? 1.5 : 1}
onClick={() => setActive(!active)}
onPointerOver={() => setHover(true)}
onPointerOut={() => setHover(false)}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
</mesh>
);
}
Scroll-Controlled Animation
import { useScroll } from '@react-three/fiber';
function ScrollAnimation() {
const meshRef = useRef();
const data = useScroll();
useFrame(() => {
meshRef.current.rotation.y = data.offset * Math.PI * 2;
});
return (
<mesh ref={meshRef}>
<torusKnotGeometry args={[1, 0.4, 100, 16]} />
<meshStandardMaterial color="teal" />
</mesh>
);
}
4. Advanced Effects
Shader Materials
import { useFrame } from '@react-three/fiber';
import * as THREE from 'three';
function ShaderBox() {
const meshRef = useRef();
const uniforms = {
u_time: { value: 0 },
u_color: { value: new THREE.Color('purple') }
};
useFrame((state) => {
const { clock } = state;
uniforms.u_time.value = clock.getElapsedTime();
});
return (
<mesh ref={meshRef}>
<boxGeometry args={[2, 2, 2]} />
<shaderMaterial
uniforms={uniforms}
vertexShader={`
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`}
fragmentShader={`
uniform vec3 u_color;
uniform float u_time;
varying vec2 vUv;
void main() {
gl_FragColor = vec4(u_color * abs(sin(u_time)), 1.0);
}
`}
/>
</mesh>
);
}
Particle Systems
function Particles() {
const particlesRef = useRef();
const count = 5000;
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i++) {
positions[i] = (Math.random() - 0.5) * 10;
colors[i] = Math.random();
}
useFrame((state) => {
particlesRef.current.rotation.y += 0.001;
});
return (
<points ref={particlesRef}>
<bufferGeometry>
<bufferAttribute
attach="attributes-position"
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
attach="attributes-color"
count={colors.length / 3}
array={colors}
itemSize={3}
/>
</bufferGeometry>
<pointsMaterial
size={0.05}
vertexColors
transparent
alphaTest={0.01}
/>
</points>
);
}
5. Performance Optimization
Instanced Meshes
function InstancedCubes({ count = 1000 }) {
const meshRef = useRef();
const dummy = new THREE.Object3D();
useEffect(() => {
for (let i = 0; i < count; i++) {
dummy.position.set(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
Math.random() * 10 - 5
);
dummy.updateMatrix();
meshRef.current.setMatrixAt(i, dummy.matrix);
}
meshRef.current.instanceMatrix.needsUpdate = true;
}, [count]);
return (
<instancedMesh ref={meshRef} args={[null, null, count]}>
<boxGeometry args={[0.2, 0.2, 0.2]} />
<meshStandardMaterial color="blue" />
</instancedMesh>
);
}
Level of Detail (LOD)
import { LOD } from 'three';
function AdaptiveModel() {
const lodRef = useRef();
useEffect(() => {
const lod = lodRef.current;
lod.addLevel(new THREE.Mesh(
new THREE.SphereGeometry(1, 32, 32),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
), 20);
lod.addLevel(new THREE.Mesh(
new THREE.SphereGeometry(1, 16, 16),
new THREE.MeshBasicMaterial({ color: 0x00ff00 })
), 10);
lod.addLevel(new THREE.Mesh(
new THREE.SphereGeometry(1, 8, 8),
new THREE.MeshBasicMaterial({ color: 0x0000ff })
), 5);
}, []);
return <primitive object={new LOD()} ref={lodRef} />;
}
6. Integration with React State
Controlled 3D Components
function ColorChanger() {
const [color, setColor] = useState('pink');
return (
<>
<mesh>
<sphereGeometry args={[1, 32, 32]} />
<meshStandardMaterial color={color} />
</mesh>
<Html>
<select onChange={(e) => setColor(e.target.value)}>
<option value="pink">Pink</option>
<option value="blue">Blue</option>
<option value="green">Green</option>
</select>
</Html>
</>
);
}
React-Three-Fiber Events
function InteractiveScene() {
const [position, setPosition] = useState([0, 0, 0]);
return (
<Canvas
onPointerMissed={() => setPosition([0, 0, 0])}
>
<mesh
position={position}
onClick={(e) => {
e.stopPropagation();
setPosition([Math.random() * 5, Math.random() * 5, 0]);
}}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="orange" />
</mesh>
</Canvas>
);
}
7. Combining 2D and 3D
HTML Overlays with drei
import { Html } from '@react-three/drei';
function ModelWithLabel() {
return (
<mesh>
<boxGeometry args={[2, 2, 2]} />
<meshStandardMaterial color="skyblue" />
<Html distanceFactor={10}>
<div className="label">Click me!</div>
</Html>
</mesh>
);
}
/* CSS */
.label {
background: white;
padding: 8px 16px;
border-radius: 4px;
pointer-events: none;
}
3D in React Components
function ProductViewer() {
const [rotation, setRotation] = useState([0, 0, 0]);
return (
<div className="product-viewer">
<div className="controls">
<button onClick={() => setRotation([0, rotation[1] + 0.1, 0])}>
Rotate
</button>
</div>
<div className="canvas-container">
<Canvas>
<ambientLight intensity={0.5} />
<mesh rotation={rotation}>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color="gold" />
</mesh>
</Canvas>
</div>
</div>
);
}
Best Practices for Three.js in React
- Memory Management: Always clean up geometries, materials, and textures
- Performance Monitoring: Use Three.js stats.js or react-three-fiber’s perf
- Responsive Design: Handle resize events and adjust camera/renderer
- Asset Optimization: Compress textures and simplify geometries
- Progressive Enhancement: Provide fallbacks for slower devices
- Code Splitting: Lazy load heavy 3D components
- Accessibility: Add ARIA labels and keyboard controls
Troubleshooting Common Issues
Problem: Black screen
- Solution: Check lights and camera position
- Solution: Verify renderer is attached to DOM
Problem: Low framerate
- Solution: Reduce polygon count
- Solution: Use instanced meshes for repeated objects
- Solution: Implement LOD (Level of Detail)
Problem: Models not loading
- Solution: Check CORS headers on model files
- Solution: Verify file paths and formats
Problem: Z-fighting
- Solution: Adjust near/far camera planes
- Solution: Add small offsets to overlapping geometries
By leveraging these Three.js techniques in React, you can create rich 3D experiences that are performant and maintainable. The react-three-fiber library provides an especially elegant way to work with Three.js in a React-centric way, allowing you to compose 3D scenes declaratively using familiar React patterns.