OrbitControls
Three.js ships a family of camera controls in three/examples: OrbitControls (turntable), FlyControls (free-fly), FirstPersonControls, TrackballControls, PointerLockControls (FPS).
OrbitControls + PointerLockControls walkthrough
EXAMPLE
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';
import { FlyControls } from 'three/examples/jsm/controls/FlyControls.js';
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js';
// 1) Standard setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 1000);
camera.position.set(5, 5, 10);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
// 2) OrbitControls — turntable around a target (perfect for product viewers)
const orbit = new OrbitControls(camera, renderer.domElement);
orbit.target.set(0, 0, 0);
orbit.enableDamping = true; // smooth easing
orbit.dampingFactor = 0.05;
orbit.minDistance = 2;
orbit.maxDistance = 50;
orbit.maxPolarAngle = Math.PI / 2.05; // don't go below the floor
orbit.enablePan = true;
orbit.panSpeed = 0.7;
orbit.rotateSpeed = 0.8;
orbit.zoomSpeed = 1.0;
orbit.update();
// Animation loop must call update() if damping is on
function tick() {
orbit.update();
renderer.render(scene, camera);
requestAnimationFrame(tick);
}
tick();
// 3) PointerLockControls — first-person, locks the mouse cursor (FPS games)
const fps = new PointerLockControls(camera, renderer.domElement);
const overlay = document.querySelector('#play-overlay');
overlay.addEventListener('click', () => fps.lock());
fps.addEventListener('lock', () => overlay.style.display = 'none');
fps.addEventListener('unlock', () => overlay.style.display = 'flex');
// Manually wire WASD movement (PointerLockControls doesn't move; it just rotates)
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
const keys = {};
addEventListener('keydown', (e) => keys[e.code] = true);
addEventListener('keyup', (e) => keys[e.code] = false);
let prev = performance.now();
function step() {
const now = performance.now();
const dt = (now - prev) / 1000; prev = now;
if (fps.isLocked) {
velocity.x -= velocity.x * 10 * dt;
velocity.z -= velocity.z * 10 * dt;
direction.z = Number(keys['KeyW']) - Number(keys['KeyS']);
direction.x = Number(keys['KeyD']) - Number(keys['KeyA']);
direction.normalize();
if (keys['KeyW'] || keys['KeyS']) velocity.z -= direction.z * 50 * dt;
if (keys['KeyA'] || keys['KeyD']) velocity.x -= direction.x * 50 * dt;
fps.moveRight(-velocity.x * dt);
fps.moveForward(-velocity.z * dt);
}
renderer.render(scene, camera);
requestAnimationFrame(step);
}
step();
// 4) FlyControls — free 6-DOF flight (drone-like)
const fly = new FlyControls(camera, renderer.domElement);
fly.movementSpeed = 10;
fly.rollSpeed = Math.PI / 8;
fly.dragToLook = true;
// Update with dt in the loop: fly.update(dt);
// 5) TransformControls — gizmo for object manipulation (editor UIs)
const gizmo = new TransformControls(camera, renderer.domElement);
gizmo.attach(mesh);
scene.add(gizmo);
gizmo.addEventListener('dragging-changed', (e) => {
orbit.enabled = !e.value; // pause orbit while dragging the gizmo
});
// Keybindings (T/R/S for translate/rotate/scale)
addEventListener('keydown', (e) => {
if (e.code === 'KeyT') gizmo.setMode('translate');
if (e.code === 'KeyR') gizmo.setMode('rotate');
if (e.code === 'KeyS') gizmo.setMode('scale');
});
// 6) Handle resize for all controls
addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
});
// 7) Choose your controls
// Product viewer / data viz → OrbitControls (with limited polar angle)
// FPS / immersive scene → PointerLockControls + custom movement
// Drone / flight sim → FlyControls
// 3D modeller / editor → TransformControls + OrbitControls
// Touch-friendly mobile → OrbitControls (built-in touch support)
// 8) Common pitfalls
// • Damping on but update() not called → controls feel frozen
// • Resize handler missing → camera aspect drifts
// • Multiple controls on the same camera fighting each other
// • Forgetting to .dispose() controls on scene teardown
Why it matters
OrbitControls with damping + a clamped polar angle is the go-to for product viewers. For immersive games or editors, layer the right control type per concern (FPS for view, TransformControls for objects).
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
Try it Yourself »
Exercise
Camera orbit/pan/zoom helper.
new
(camera, renderer.domElement);
PascalCase, 13 chars.
Discussion
Loading…