Three.js — WebGL 3D Scenes Skill
When to use
-
Real 3D: product spins, interactive hero scenes, shaders/material effects, 3D data viz
-
You need full control beyond “background effects”
-
You can budget time for asset pipeline + performance tuning
Core mental model
-
Create:
-
Scene (root graph)
-
Camera (Perspective/Orthographic)
-
Renderer (WebGLRenderer )
-
Mesh = Geometry
- Material
-
Lights (if using non-unlit materials)
-
Render loop:
-
requestAnimationFrame(animate)
-
Update time-based animations, controls, mixers, then renderer.render(scene, camera)
Key APIs/patterns
-
Setup:
-
const renderer = new THREE.WebGLRenderer({ canvas, antialias, alpha })
-
renderer.setSize(width, height, false)
-
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
-
Camera:
-
camera.aspect = width / height; camera.updateProjectionMatrix()
-
Scene graph:
-
scene.add(object) / object.position/rotation/scale
-
Loading assets:
-
GLTFLoader (models), TextureLoader (images), DRACOLoader (compressed glTF)
-
Controls (common):
-
OrbitControls (debug/product), PointerLockControls (FPS), custom pointer handlers
-
Cleanup (important in SPAs):
-
geometry.dispose() , material.dispose() , texture.dispose() , renderer.dispose()
-
Remove event listeners; cancel RAF.
Common pitfalls
-
Not handling resize → stretched/cropped rendering
-
Too high devicePixelRatio → mobile GPU meltdown
-
Leaking WebGL resources (not disposing) → crashes after route changes
-
Loading huge textures/models → slow start; use compressed textures, Draco/KTX2, smaller maps
-
Using too many lights/shadows → expensive; fake lighting with baked textures when possible
Quick recipes
- Minimal spinning cube
import * as THREE from "three";
const canvas = document.querySelector("#c"); const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100); camera.position.set(0, 0, 4);
const geom = new THREE.BoxGeometry(1, 1, 1); const mat = new THREE.MeshStandardMaterial({ color: 0x7c3aed }); const mesh = new THREE.Mesh(geom, mat); scene.add(mesh);
scene.add(new THREE.AmbientLight(0xffffff, 0.8)); const dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(2, 2, 2); scene.add(dir);
function resize() { const w = canvas.clientWidth; const h = canvas.clientHeight; renderer.setSize(w, h, false); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); camera.aspect = w / h; camera.updateProjectionMatrix(); } window.addEventListener("resize", resize); resize();
function animate(t) { mesh.rotation.y = t * 0.0006; renderer.render(scene, camera); requestAnimationFrame(animate); } requestAnimationFrame(animate);
- Respect reduced motion
- If prefers-reduced-motion: reduce , render a still frame (no RAF) or slow updates.
What to ask the user
-
Is this decorative (hero) or functional 3D (product viewer)?
-
Target devices: mobile? older iPhones?
-
Asset format availability (glTF, HDRI, textures) and file size constraints
-
Accessibility/reduced motion requirements