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

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);

Test yourself

Q1. OrbitControls let you…
Q2. OrbitControls live in…
Q3. For first-person movement, prefer…

Discussion

Loading…