iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Exercises

Six small builds that flex the parts of three.js you actually use day-to-day. Each is solvable in 20–30 minutes; the goal is to internalise the boring-but-essential setup that turns a beautiful demo into a stable scene.

Six three.js drills with hints

EXAMPLE
import * as THREE from 'three';

// ============================================================
// Drill 1 — Correct colour space + tonemapping
// ============================================================
// TASK: set up a renderer so JPG/PNG textures look correct (not washed out).
// HINTS:
//   - renderer.outputColorSpace = THREE.SRGBColorSpace
//   - renderer.toneMapping       = THREE.ACESFilmicToneMapping
//   - each loaded texture: tex.colorSpace = THREE.SRGBColorSpace
//
// SOLUTION:
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;

// ============================================================
// Drill 2 — Resize handler that keeps aspect ratio correct
// ============================================================
function onResize(camera, renderer) {
  renderer.setSize(innerWidth, innerHeight);
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
}
addEventListener('resize', () => onResize(camera, renderer));
// Common bug: forgetting updateProjectionMatrix() -> objects stretch on resize.

// ============================================================
// Drill 3 — Animate without setTimeout drift
// ============================================================
const clock = new THREE.Clock();
function frame() {
  const dt = clock.getDelta();        // seconds since last frame
  mesh.rotation.y += dt * 0.5;        // dt-driven rotation = framerate independent
  renderer.render(scene, camera);
}
renderer.setAnimationLoop(frame);

// ============================================================
// Drill 4 — Load a GLB and play its first animation
// ============================================================
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
const loader = new GLTFLoader();
let mixer;
loader.load('/model.glb', (gltf) => {
  scene.add(gltf.scene);
  mixer = new THREE.AnimationMixer(gltf.scene);
  mixer.clipAction(gltf.animations[0]).play();
});
function tick(dt) { mixer?.update(dt); }
// Common bug: forgetting mixer.update -> animation never plays.

// ============================================================
// Drill 5 — Raycast clicks to highlight objects
// ============================================================
const ray = new THREE.Raycaster();
const pointer = new THREE.Vector2();
let highlighted;
addEventListener('pointerdown', (e) => {
  pointer.set((e.clientX/innerWidth)*2-1, -(e.clientY/innerHeight)*2+1);
  ray.setFromCamera(pointer, camera);
  const hit = ray.intersectObjects(scene.children, true)[0]?.object;
  if (hit !== highlighted) {
    highlighted?.material?.emissive?.setHex(0x000000);
    hit?.material?.emissive?.setHex(0x224488);
    highlighted = hit;
  }
});

// ============================================================
// Drill 6 — Dispose properly when a scene changes
// ============================================================
function dispose(obj) {
  obj.traverse((node) => {
    if (node.geometry) node.geometry.dispose();
    if (Array.isArray(node.material)) node.material.forEach((m) => m.dispose());
    else if (node.material) node.material.dispose();
  });
  scene.remove(obj);
}
// Without this, switching scenes leaks GPU memory until the browser kills the tab.

// ============================================================
// Scoring
// ============================================================
// 6 / 6 -> ready to ship to production
// 4 / 6 -> revisit the colour-management + lifecycle docs
// < 4   -> three.js fundamentals course on three-fundamentals.org

Why it matters

Three boilerplate fixes — correct colour space, dt-driven animation, and dispose() on scene change — turn 90% of \"my three.js demo eats my MacBook\" complaints into stable scenes. They are unglamorous and they ship with every production build.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Fill in: const ____ = new THREE.Scene();
Try it Yourself »

Discussion

Loading…