What is 3D on the web?
WebGL lets the browser draw hardware-accelerated 3D using the GPU. It is powerful, but the raw API is verbose: you manage buffers, write shaders and handle the maths yourself. Three.js wraps WebGL in a friendly scene graph so you can describe objects, lights and cameras and let the library handle the rest.
The result is that 3D on the web is now practical for product configurators, data visualisation, interactive storytelling, games and immersive WebXR experiences. It is not the right choice for every site, but when motion and space tell the story better than a flat page, Three.js is the standard tool.
Scene, camera, renderer
Every Three.js app has three core objects.
// setup.js
import * as THREE from "three";
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 100);
camera.position.set(0, 0, 3);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
The scene is the container. The camera defines the view; PerspectiveCamera gives realistic depth, while OrthographicCamera is useful for flat or isometric views. The renderer draws the scene to a canvas. Clamping the pixel ratio keeps high-density screens from rendering more pixels than they need.
Meshes, geometry and materials
A visible object is a mesh: a geometry for its shape and a material for its surface.
// mesh.js
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x22c55e, roughness: 0.4 }),
);
cube.position.set(0, 0, 0);
scene.add(cube);
Three.js ships many built-in geometries (box, sphere, plane, torus and more) and materials for different looks. MeshStandardMaterial responds to lights and is a good default; MeshBasicMaterial ignores lighting and is cheaper. Meshes can be grouped and nested, forming the scene graph that transforms inherit through.
Lights and shadows
Without light, most materials render black.
// lights.js
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
const key = new THREE.DirectionalLight(0xffffff, 2.5);
key.position.set(2, 3, 2);
scene.add(ambient, key);
Lights are objects in the scene, so their position and type matter. Shadows require enabling renderer.shadowMap and marking the light, the object and the surface, and they are relatively expensive, so use them deliberately. Every light adds cost, so keep the count low.
Models and textures
Real projects usually load assets rather than build geometry by hand. glTF is the standard web 3D format.
// load.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
loader.load("/models/robot.glb", (gltf) => {
scene.add(gltf.scene);
});
Textures load similarly with TextureLoader, and RGBELoader or PMREMGenerator can supply environment maps for realistic reflections. Keep model and texture sizes reasonable, since they dominate load time and memory.
The animation loop
Animation updates the scene and re-renders it each frame.
// loop.js
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
const delta = clock.getDelta();
cube.rotation.y += delta;
renderer.render(scene, camera);
});
Using delta keeps motion speed consistent regardless of frame rate. When the scene is static, render on demand instead of continuously: render after a change, a control interaction or an animation, and stop when nothing is happening. That saves battery and GPU on mobile.
Interaction
To make objects clickable or hoverable, use a Raycaster.
// pick.js
const raycaster = new THREE.Raycaster();
const pointer = new THREE.Vector2();
addEventListener("pointerdown", (event) => {
pointer.x = (event.clientX / innerWidth) * 2 - 1;
pointer.y = -(event.clientY / innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const hit = raycaster.intersectObjects(scene.children)[0];
if (hit) hit.object.material.color.set(0xf97316);
});
Raycasting from the camera through the pointer returns the objects under it, which is enough for selection, highlighting and simple interactions.
Performance and cleanup
3D is the most expensive thing most sites will ever render, so discipline matters.
- Dispose geometries, materials and textures when you remove objects; they live in GPU memory.
- Reuse geometry and materials across instances instead of creating new ones.
- Merge static geometry and use instancing for repeated objects.
- Limit lights and shadow-casting objects.
- Lower the pixel ratio on high-density displays.
- Render on demand for static scenes.
- Pause rendering when the canvas is offscreen.
// dispose.js
mesh.geometry.dispose();
mesh.material.dispose();
scene.remove(mesh);
Forgetting to dispose is the most common cause of a Three.js app that slowly consumes all available memory.
React Three Fiber
With React, React Three Fiber renders Three.js declaratively, so the scene is described with components and integrates with React state and lifecycle.
// Scene.jsx
import { Canvas } from "@react-three/fiber";
import { OrbitControls } from "@react-three/drei";
export function Scene() {
return (
<Canvas>
<ambientLight intensity={0.4} />
<directionalLight position={[2, 3, 2]} intensity={2.5} />
<mesh>
<boxGeometry />
<meshStandardMaterial color="#22c55e" />
</mesh>
<OrbitControls />
</Canvas>
);
}
The drei helpers provide controls, environments and common abstractions. It is the recommended way to use Three.js in React.
Best practices
- Clamp the pixel ratio and resize the renderer with the window.
- Reuse geometry and materials; instance repeated objects.
- Dispose of GPU resources when removing objects.
- Keep lights and shadow casters to a minimum.
- Render on demand for static scenes.
- Test on real mid-range mobile devices.
- Provide a fallback when WebGL is unavailable.
Common mistakes
- Forgetting to dispose and leaking GPU memory.
- Rendering continuously when nothing changes.
- Creating new geometry or materials every frame.
- Adding many lights and shadows without measuring.
- Loading huge unoptimised models and textures.
- Testing only on a fast desktop and shipping to mobile.
Where to go next
Three.js opens a whole dimension for the web. Add choreographed motion with the Advanced Animation guide, build with React Three Fiber if you use React, and keep the cost in check with the Web Performance guide. Then build the smallest possible scene — a lit cube you can orbit — and grow it one concept at a time.