Skip to main content

Command Palette

Search for a command to run...

Mastering Camera Movement in Three.js

Updated
β€’10 min read
Mastering Camera Movement in Three.js

Hey there! πŸ‘‹ Welcome back to my Three.js learning journey. So far, I've covered the basics, geometries, materials, lighting, OrbitControls, and transformations. Today, I'm diving into something that completely changed how I think about 3D scenes: camera movement.

What a Camera Really Is

Before we get into movement, let's establish something important: camera isn't just a passive observer, it’s just another object in your 3D scene.

It has position, rotation, and direction - just like any mesh. The camera simply defines:

  • Where the viewer is (position)

  • What the viewer looks at (rotation/direction)

  • How motion is perceived (field of view, near/far planes)

The Movie Analogy

Think of your Three.js scene like a movie set. The camera is literally the camera - it can be:

  • Static (fixed on a tripod, filming one angle)

  • Orbiting (on a circular track around the actors)

  • Following (mounted on a dolly, tracking a character)

  • First-person (handheld, the viewer IS the character)

Good camera movement makes even simple scenes feel alive and cinematic. Bad camera movement? It breaks the immersion instantly.

Static Camera: The Foundation

Let's start with the simplest approach - a camera that doesn't move at all.

When to Use It

  • Demos and tutorials

  • Debugging your scene

  • UI-like interfaces

  • Scenes where the objects move, not the viewer

Setting Up a Static Camera

import * as THREE from "three";

const canvas = document.querySelector("#canvas");

// Scene and renderer
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(window.innerWidth, window.innerHeight);

// Create camera
const camera = new THREE.PerspectiveCamera(
    75,                                  // Field of view (degrees)
    window.innerWidth / window.innerHeight,  // Aspect ratio
    0.1,                                 // Near clipping plane
    1000                                  // Far clipping plane
);

// Position the camera
camera.position.set(0, 2, 6);  // x=0, y=2 (slightly up), z=6 (towards you)

// Make camera look at a point
camera.lookAt(0, 0, 0);  // Look at world origin

// Create a simple object to view
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x44aa88 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

// Add light
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

// Render
function animate() {
    requestAnimationFrame(animate);

    // Rotate cube for visual interest
    cube.rotation.y += 0.01;

    renderer.render(scene, camera);
}

animate();

The Magic of lookAt()

The lookAt() method is powerful - it automatically rotates the camera so it points at a specific position. Without it, the camera might be facing the wrong direction even if positioned correctly.

camera.lookAt(0, 0, 0);  // Look at origin
camera.lookAt(cube.position);  // Look at the cube
camera.lookAt(new THREE.Vector3(5, 2, -3));  // Look at specific point

OrbitControls: User-Controlled Camera

OrbitControls is probably the most common camera system you'll use. It lets users rotate around a target point, zoom in/out, and pan - just like in 3D modeling software.

When to Use It

  • Product viewers (360Β° product inspection)

  • 3D editors and tools

  • Architectural visualizations

  • Any scene where users need to explore

Setting Up OrbitControls

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

const canvas = document.querySelector("#canvas");
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(window.innerWidth, window.innerHeight);

// Camera
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
camera.position.set(0, 2, 6);

// Create OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);

// Set what point to orbit around
controls.target.set(0, 0, 0);

// Enable smooth damping (inertia)
controls.enableDamping = true;
controls.dampingFactor = 0.05;  // Lower = more inertia

// Create scene objects
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x44aa88 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 5, 5);
scene.add(light);

// Animation loop
function animate() {
    requestAnimationFrame(animate);

    // IMPORTANT: Update controls when damping is enabled
    controls.update();

    renderer.render(scene, camera);
}

animate();

Key Concepts

Camera Orbits Around Target, Not Itself

This is crucial to understand. The camera doesn't rotate around its own position - it rotates around the target point. Imagine the camera is on a sphere, and it moves along that sphere's surface while always looking at the center.

Damping Simulates Inertia

Without damping, the camera stops instantly when you release the mouse - feels robotic. With damping, it gradually slows down - feels natural and smooth. Don't forget to call controls.update() in your animation loop when damping is enabled!

Manual Camera Orbit: Taking Control

OrbitControls is great, but what if you want a camera that orbits automatically, or follows a specific path? Time to use some math!

When to Use It

  • Automated camera animations

  • Cinematic sequences

  • Scripted tours

  • Custom camera behaviors

Creating a Circular Orbit

let time = 0;
const radius = 6;  // How far from center

function animate() {
    requestAnimationFrame(animate);

    time += 0.01;  // Speed of rotation

    // Calculate position on a circle using sin/cos
    camera.position.x = Math.cos(time) * radius;
    camera.position.z = Math.sin(time) * radius;
    camera.position.y = 2;  // Height above ground

    // Always look at center
    camera.lookAt(0, 0, 0);

    renderer.render(scene, camera);
}

animate();

Understanding the Math

  • Math.cos(time) and Math.sin(time) create circular motion

  • Multiply by radius to control distance from center

  • Increment time to move around the circle

  • lookAt() keeps the camera pointed at the center

This is the foundation of all cinematic camera movements! You can modify this to create ellipses, figure-8 patterns, or complex paths.

Camera Follow System: Tracking Objects

Ever played a racing game where the camera follows your car? That's a follow system.

When to Use It

  • Games (following the player)

  • Simulations (tracking moving objects)

  • Animated scenes

  • Product showcases with moving elements

Basic Follow Camera

// Create a target object to follow
const targetGeometry = new THREE.SphereGeometry(0.5);
const targetMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 });
const target = new THREE.Mesh(targetGeometry, targetMaterial);
scene.add(target);

// Camera offset from target
const offset = new THREE.Vector3(0, 2, 5);  // Behind and above

function animate() {
    requestAnimationFrame(animate);

    // Move the target (simulate player movement)
    target.position.x += 0.02;

    // Camera follows target with offset
    camera.position.copy(target.position).add(offset);

    // Look at the target
    camera.lookAt(target.position);

    renderer.render(scene, camera);
}

animate();

How It Works

  1. The target object moves around the scene

  2. Every frame, we copy the target's position

  3. Add an offset to position the camera behind/above

  4. Use lookAt() to point the camera at the target

The camera is now "attached" to the target with a fixed offset.

Smooth Camera Follow: Adding Polish

The basic follow camera works, but it feels robotic - the camera snaps to follow the target instantly. Let's make it smooth!

Linear Interpolation (Lerp)

Lerp smoothly blends between two values. Instead of instantly moving to the target position, we move partway there each frame.

const offset = new THREE.Vector3(0, 2, 5);
const desiredPosition = new THREE.Vector3();

function animate() {
    requestAnimationFrame(animate);

    // Move target
    target.position.x += 0.02;

    // Calculate where camera WANTS to be
    desiredPosition.copy(target.position).add(offset);

    // Smoothly move camera toward desired position
    camera.position.lerp(desiredPosition, 0.1);

    // Look at target
    camera.lookAt(target.position);

    renderer.render(scene, camera);
}

animate();

Understanding Lerp

camera.position.lerp(desiredPosition, 0.1);

The second parameter (0.1) controls the blend:

  • 0.1 = Move 10% of the way each frame (slow, heavy feel)

  • 0.5 = Move 50% of the way each frame (medium)

  • 0.9 = Move 90% of the way each frame (fast, snappy)

Lower values create a "camera on a spring" effect - it lags behind and smoothly catches up. This feels natural and cinematic!

First-Person Camera: Becoming the Player

In first-person games, the camera IS the player. Movement happens relative to where the camera is looking.

When to Use It

  • First-person games

  • Walkthroughs

  • Immersive experiences

  • VR applications

Basic First-Person Movement

const speed = 0.05;
const keys = {};

// Track key presses
window.addEventListener('keydown', (e) => keys[e.key] = true);
window.addEventListener('keyup', (e) => keys[e.key] = false);

function animate() {
    requestAnimationFrame(animate);

    // Move forward (in camera's local forward direction)
    if (keys['w']) {
        camera.translateZ(-speed);  // Negative Z is forward
    }

    // Move backward
    if (keys['s']) {
        camera.translateZ(speed);
    }

    // Strafe left
    if (keys['a']) {
        camera.translateX(-speed);
    }

    // Strafe right
    if (keys['d']) {
        camera.translateX(speed);
    }

    renderer.render(scene, camera);
}

animate();

Important: Local vs World Space

translateZ() and translateX() move in local space - relative to the camera's current rotation. This is perfect for first-person movement because:

  • Forward is always "where the camera is looking"

  • Left/right moves perpendicular to that direction

  • It feels natural and intuitive

If you used world space (position.z += speed), the camera would always move in the same global direction regardless of where it's facing - very disorienting!

Common Mistakes (I Made These!)

1. Forgetting lookAt()

When manually moving the camera, you need to call lookAt() to point it at something. Otherwise, it might face the wrong direction.

// Wrong - camera might face the wrong way
camera.position.set(5, 5, 5);

// Right - camera points at target
camera.position.set(5, 5, 5);
camera.lookAt(0, 0, 0);

2. Mixing Controls and Manual Movement

Don't use OrbitControls AND manually move the camera. Pick one approach! They'll fight each other.

// Don't do this!
const controls = new OrbitControls(camera, canvas);
camera.position.x += 0.1;  // Conflicts with controls

3. Moving Camera Without Updating Target

If you manually move the camera while using OrbitControls, update the target too:

camera.position.set(10, 5, 10);
controls.target.set(0, 0, 0);
controls.update();  // Important!

4. Large Lerp Values Causing Motion Sickness

Using lerp values above 0.3 can make the camera move too quickly and feel jittery. Keep it smooth:

// Too fast - feels jarring
camera.position.lerp(target, 0.8);

// Better - smooth and natural
camera.position.lerp(target, 0.1);

When to Use Each Approach

Static Camera

  • Simple demos

  • Debugging

  • Fixed viewpoint scenes

OrbitControls

  • Product viewers

  • User exploration

  • 3D editors

  • Most interactive scenes

Manual Orbit

  • Automated rotations

  • Cinematic sequences

  • Custom animations

  • Scripted camera paths

Follow Camera

  • Games

  • Tracking simulations

  • Following moving objects

Smooth Follow (Lerp)

  • Polished game cameras

  • Professional animations

  • Any time you want smooth, natural motion

First-Person

  • FPS games

  • Walkthroughs

  • Immersive experiences

What I Learned

1. The Camera Is Just an Object

This was a lightbulb moment for me. The camera has position and rotation like everything else - there's nothing magical about it. Understanding this makes camera control much less intimidating.

2. Movement Defines Emotion

A slow, smooth camera feels calm and professional. A fast, jerky camera feels chaotic or amateurish. The way you move the camera dramatically affects how your scene is perceived.

3. Math Gives You Control

OrbitControls is convenient, but using sin/cos to calculate camera positions gives you complete creative control. Every cinematic camera movement is just math!

4. Lerp Is Your Friend

Linear interpolation smooths out almost any motion. Whether following objects or moving between positions, lerp makes everything feel more polished and professional.

5. Local Space vs World Space Matters

Understanding the difference between translateZ() (local) and position.z += (world) is crucial, especially for first-person cameras.

6. Don't Mix Control Methods

Pick one camera control approach and stick with it. Mixing OrbitControls with manual movement creates conflicts and weird behavior.

7. lookAt() Is Essential

Whenever you manually position the camera, remember to call lookAt() to point it at your subject. I can't tell you how many times I forgot this and wondered why my camera was facing the wrong direction!

Wrapping Up

Camera movement is one of those things that seems simple until you really dive into it. But once you understand that the camera is just another object, and you learn a few key techniques (static, orbit, follow, lerp), you can create any camera behavior you can imagine.

Start with static cameras to understand positioning and lookAt(). Then experiment with OrbitControls to see how interactive cameras work. Once you're comfortable, try creating a manual orbit with sin/cos. Finally, build a smooth follow camera with lerp.

The key is understanding that how you move the camera is just as important as what you're showing. Great camera work makes simple scenes feel cinematic!

Try building:

  • A product viewer with OrbitControls

  • An automatic turntable with manual orbit

  • A simple game with a follow camera

  • A first-person walkthrough

Once you master camera control, you'll see how it connects with everything else!

Happy filming! πŸŽ₯