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)andMath.sin(time)create circular motionMultiply by
radiusto control distance from centerIncrement
timeto move around the circlelookAt()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
The target object moves around the scene
Every frame, we copy the target's position
Add an offset to position the camera behind/above
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! π₯



