
Threejs Syntax Controls
- 18 installs
- 11 repo stars
- Updated July 8, 2026
- openaec-foundation/three.js-claude-skill-package
Helps with ai & agent building tasks.
About
threejs-syntax-controls is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- threejs-syntax-controls
- AI & Agent Building
- AI-coding skill
Threejs Syntax Controls by the numbers
- 18 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #10,710 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/three.js-claude-skill-package --skill threejs-syntax-controlsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 18 |
|---|---|
| repo stars | ★ 11 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/three.js-claude-skill-package ↗ |
What it does
Helps with ai & agent building tasks.
Files
threejs-syntax-controls
Quick Reference
Control Selection Decision Tree
| Use Case | Control | Why |
|---|---|---|
| Inspect a 3D model from all angles | OrbitControls | Orbit/pan/zoom around a target point |
| Top-down map or 2D-style navigation | MapControls | Left-drag pans, right-drag rotates |
| Free-flight editor or space scene | FlyControls | Six degrees of freedom, WASD + mouse |
| First-person game with pointer lock | PointerLockControls | Hides cursor, captures mouse movement |
| First-person without pointer lock | FirstPersonControls | Mouse-look without browser lock API |
| Move/rotate/scale objects via gizmo | TransformControls | Interactive translate/rotate/scale handles |
| Drag objects along a plane | DragControls | Click-and-drag object repositioning |
| Unconstrained rotation (no gimbal lock) | ArcballControls | Full spherical rotation with animation |
| Unconstrained rotation (simpler) | TrackballControls | Like OrbitControls but no pole constraint |
Import Paths (Three.js r160+)
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { MapControls } from 'three/addons/controls/MapControls.js';
import { FlyControls } from 'three/addons/controls/FlyControls.js';
import { FirstPersonControls } from 'three/addons/controls/FirstPersonControls.js';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
import { TrackballControls } from 'three/addons/controls/TrackballControls.js';
import { ArcballControls } from 'three/addons/controls/ArcballControls.js';
import { DragControls } from 'three/addons/controls/DragControls.js';ALWAYS use 'three/addons/controls/...' for r160+. The legacy path 'three/examples/jsm/controls/...' still works but is deprecated.
Critical Warnings
ALWAYS call controls.update() in the animation loop when enableDamping or autoRotate is true. Failing to do so causes the camera to freeze after user interaction ends.
ALWAYS call controls.dispose() when removing controls. Failing to do so leaks DOM event listeners (pointermove, wheel, keydown) that cause memory leaks and ghost interactions.
NEVER attach two camera-control classes to the same camera simultaneously without disabling one. OrbitControls + FlyControls on the same camera causes erratic movement.
ALWAYS disable OrbitControls while TransformControls is dragging. Listen to the dragging-changed event and toggle orbitControls.enabled.
ALWAYS call PointerLockControls.lock() from a user gesture (click handler). Browsers reject pointer lock requests without user interaction.
ALWAYS pass delta time to FlyControls.update(delta). Passing no argument or passing elapsed time causes speed to depend on frame rate.
---
Controls Lifecycle
Every control follows the same lifecycle pattern:
construct --> configure --> attach to loop --> dispose on cleanupStep 1: Construct
const controls = new OrbitControls(camera, renderer.domElement);ALWAYS pass renderer.domElement as the second argument. Passing document or document.body causes controls to capture events globally, breaking UI overlays.
Step 2: Configure
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 50;
controls.target.set(0, 1, 0);Step 3: Update in Render Loop
function animate() {
requestAnimationFrame(animate);
controls.update(); // REQUIRED when enableDamping or autoRotate is true
renderer.render(scene, camera);
}
animate();Step 4: Dispose on Cleanup
controls.dispose();---
OrbitControls
The most commonly used control. Orbits, pans, and zooms around a target point.
Constructor
new OrbitControls(camera: THREE.Camera, domElement: HTMLElement)Key Properties
| Property | Type | Default | Description |
|---|---|---|---|
enabled | boolean | true | Enable/disable all interaction |
target | Vector3 | (0,0,0) | Orbit focus point |
enableDamping | boolean | false | Smooth inertial movement |
dampingFactor | number | 0.05 | Inertia strength (0-1) |
autoRotate | boolean | false | Auto-rotate around target |
autoRotateSpeed | number | 2.0 | Degrees/sec at 60fps |
enablePan | boolean | true | Allow panning |
enableRotate | boolean | true | Allow rotation |
enableZoom | boolean | true | Allow zooming |
minDistance | number | 0 | Min zoom distance (PerspectiveCamera) |
maxDistance | number | Infinity | Max zoom distance (PerspectiveCamera) |
minZoom | number | 0 | Min zoom (OrthographicCamera) |
maxZoom | number | Infinity | Max zoom (OrthographicCamera) |
minPolarAngle | number | 0 | Min vertical angle (radians) |
maxPolarAngle | number | Math.PI | Max vertical angle (radians) |
minAzimuthAngle | number | -Infinity | Min horizontal angle (radians) |
maxAzimuthAngle | number | Infinity | Max horizontal angle (radians) |
screenSpacePanning | boolean | true | Pan in screen plane (true) or horizontal plane (false) |
zoomToCursor | boolean | false | Zoom towards cursor position |
panSpeed | number | 1.0 | Pan speed multiplier |
rotateSpeed | number | 1.0 | Rotation speed multiplier |
zoomSpeed | number | 1.0 | Zoom speed multiplier |
mouseButtons | object | {LEFT: ROTATE, MIDDLE: DOLLY, RIGHT: PAN} | Mouse button mapping |
touches | object | {ONE: ROTATE, TWO: DOLLY_PAN} | Touch gesture mapping |
keys | object | {LEFT, UP, RIGHT, BOTTOM} | Arrow key codes for panning |
Methods
| Method | Signature | Description |
|---|---|---|
update(deltaTime?) | (number?) => boolean | Update controls state. MUST call every frame with damping/autoRotate |
dispose() | () => void | Remove all event listeners |
saveState() | () => void | Save current camera position/target/zoom |
reset() | () => void | Restore to last saved state |
getDistance() | () => number | Distance from camera to target |
getPolarAngle() | () => number | Vertical angle in radians |
getAzimuthalAngle() | () => number | Horizontal angle in radians |
listenToKeyEvents(el) | (HTMLElement) => void | Enable keyboard panning |
stopListenToKeyEvents() | () => void | Disable keyboard panning |
Events
| Event | Trigger |
|---|---|
change | Camera position or target changed |
start | User interaction began (pointerdown) |
end | User interaction ended (pointerup) |
---
MapControls
Subclass of OrbitControls optimized for top-down map navigation.
| Button | OrbitControls | MapControls |
|---|---|---|
| Left mouse | Rotate | Pan |
| Middle mouse | Dolly | Dolly |
| Right mouse | Pan | Rotate |
All properties, methods, and events are identical to OrbitControls. The ONLY differences are the default mouseButtons mapping and screenSpacePanning defaulting to true.
---
FlyControls
Six-degrees-of-freedom flight camera. WASD for movement, QE for roll, RF for up/down.
Key Properties
| Property | Type | Default | Description |
|---|---|---|---|
movementSpeed | number | 1.0 | Translation speed |
rollSpeed | number | 0.005 | Roll rotation speed |
dragToLook | boolean | false | Require mouse drag to rotate (vs. always follow mouse) |
autoForward | boolean | false | Move forward automatically |
Methods
update(delta)-- ALWAYS passdeltatime from the clock. NEVER omit this argument.dispose()-- Remove event listeners.
---
PointerLockControls
First-person camera using the Pointer Lock API. Hides and captures the mouse cursor.
Key Properties
| Property | Type | Default | Description |
|---|---|---|---|
isLocked | boolean | read-only | Whether pointer is currently locked |
maxPolarAngle | number | Math.PI | Max vertical look angle |
minPolarAngle | number | 0 | Min vertical look angle |
pointerSpeed | number | 1.0 | Mouse sensitivity multiplier |
Methods
| Method | Description |
|---|---|
lock() | Request pointer lock (MUST call from user gesture) |
unlock() | Exit pointer lock |
connect() | Attach event listeners |
disconnect() | Remove event listeners |
dispose() | Full cleanup (calls disconnect) |
getObject() | Returns the controlled camera |
getDirection(target) | Write look direction into target Vector3 |
moveForward(distance) | Move camera forward |
moveRight(distance) | Move camera sideways |
Events
| Event | Trigger |
|---|---|
change | Camera orientation changed |
lock | Pointer lock acquired |
unlock | Pointer lock released |
ALWAYS implement your own WASD movement in the animation loop. PointerLockControls handles look direction only, NOT position.
---
TransformControls
Interactive gizmo for moving, rotating, and scaling scene objects.
Key Properties
| Property | Type | Default | Description |
|---|---|---|---|
mode | string | 'translate' | 'translate', 'rotate', or 'scale' |
space | string | 'world' | 'world' or 'local' coordinate space |
showX | boolean | true | Show X axis handle |
showY | boolean | true | Show Y axis handle |
showZ | boolean | true | Show Z axis handle |
size | number | 1 | Gizmo visual scale |
translationSnap | `number | null` | null |
rotationSnap | `number | null` | null |
scaleSnap | `number | null` | null |
dragging | boolean | read-only | User is currently dragging |
Methods
| Method | Description |
|---|---|
attach(object) | Attach gizmo to a scene object |
detach() | Remove gizmo from current object |
setMode(mode) | Set transform mode |
setSpace(space) | Set coordinate space |
setSize(size) | Set gizmo scale |
setTranslationSnap(snap) | Set position snap |
setRotationSnap(snap) | Set rotation snap |
setScaleSnap(snap) | Set scale snap |
getRaycaster() | Access internal raycaster |
dispose() | Cleanup |
Events
| Event | Trigger |
|---|---|
change | Gizmo visual changed |
dragging-changed | event.value is true when dragging starts, false when it ends |
objectChange | Attached object's transform was modified |
mouseDown | Pointer pressed on gizmo |
mouseUp | Pointer released from gizmo |
Critical Integration Pattern
ALWAYS disable camera controls during gizmo drag:
transformControls.addEventListener('dragging-changed', (event) => {
orbitControls.enabled = !event.value;
});ALWAYS add TransformControls to the scene: scene.add(transformControls.getHelper()) or scene.add(transformControls).
---
Brief Overview: Other Controls
ArcballControls
Unconstrained rotation with animation states. No polar angle limit -- the camera rotates freely in all directions. Best for CAD-style model inspection.
TrackballControls
Like OrbitControls without the polar angle constraint. The camera can rotate past the poles. Properties: rotateSpeed, zoomSpeed, panSpeed, staticMoving, dynamicDampingFactor.
DragControls
Drag scene objects along a plane. Constructor: new DragControls(objects, camera, domElement). Events: dragstart, drag, dragend, hoveron, hoveroff.
FirstPersonControls
Mouse-look camera without pointer lock. Properties: movementSpeed, lookSpeed, activeLook, constrainVertical, verticalMin, verticalMax.
---
Reference Links
- references/methods.md -- Full API signatures for all controls
- references/examples.md -- Working code examples for each control type
- references/anti-patterns.md -- What NOT to do
Official Sources
- https://threejs.org/docs/#examples/en/controls/OrbitControls
- https://threejs.org/docs/#examples/en/controls/MapControls
- https://threejs.org/docs/#examples/en/controls/FlyControls
- https://threejs.org/docs/#examples/en/controls/PointerLockControls
- https://threejs.org/docs/#examples/en/controls/TransformControls
threejs-syntax-controls -- Anti-Patterns
Anti-Pattern 1: Forgetting controls.update() with Damping
WRONG:
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
function animate() {
requestAnimationFrame(animate);
// Missing controls.update() !
renderer.render(scene, camera);
}What happens: The camera moves while the user drags, but snaps to a stop the instant the user releases the mouse. The damping/inertia effect NEVER activates. With autoRotate, the camera NEVER rotates.
CORRECT:
function animate() {
requestAnimationFrame(animate);
controls.update(); // ALWAYS call when enableDamping or autoRotate is true
renderer.render(scene, camera);
}---
Anti-Pattern 2: Not Calling dispose()
WRONG:
function switchToFlyMode() {
// Just replace controls without cleanup
controls = new FlyControls(camera, renderer.domElement);
}What happens: The old OrbitControls' event listeners (pointermove, pointerdown, pointerup, wheel, keydown, contextmenu) remain active on renderer.domElement. This causes:
- Memory leaks from accumulated listeners
- Ghost interactions where the old controls still respond to input
- Conflicting camera movement from two control systems
CORRECT:
function switchToFlyMode() {
controls.dispose(); // ALWAYS dispose before replacing
controls = new FlyControls(camera, renderer.domElement);
}---
Anti-Pattern 3: Attaching to document Instead of renderer.domElement
WRONG:
const controls = new OrbitControls(camera, document.body);What happens: Controls capture pointer events on the entire page. Any UI elements (buttons, sliders, dropdowns) above or beside the canvas become unusable because the controls intercept their events. Scroll events on the page trigger camera zoom.
CORRECT:
const controls = new OrbitControls(camera, renderer.domElement);ALWAYS pass renderer.domElement so controls ONLY respond to events on the canvas.
---
Anti-Pattern 4: Mixing Two Camera Controls Without Disabling
WRONG:
const orbitControls = new OrbitControls(camera, renderer.domElement);
const flyControls = new FlyControls(camera, renderer.domElement);
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
flyControls.update(delta);
renderer.render(scene, camera);
}What happens: Both controls fight over the camera's position and rotation every frame. The camera jitters, teleports, or moves unpredictably. Each control overwrites what the other set.
CORRECT:
let activeControls = orbitControls;
flyControls.enabled = false; // or don't create until needed
function switchControls(type) {
activeControls.dispose();
if (type === 'fly') {
activeControls = new FlyControls(camera, renderer.domElement);
} else {
activeControls = new OrbitControls(camera, renderer.domElement);
}
}NEVER have two camera controls active simultaneously on the same camera. ALWAYS dispose or disable one before enabling another.
---
Anti-Pattern 5: Not Disabling OrbitControls During TransformControls Drag
WRONG:
const orbitControls = new OrbitControls(camera, renderer.domElement);
const transformControls = new TransformControls(camera, renderer.domElement);
scene.add(transformControls);
transformControls.attach(cube);
// No dragging-changed listener!What happens: When the user drags a TransformControls gizmo handle, OrbitControls also responds to the drag. The camera orbits while the object moves, making precise positioning impossible.
CORRECT:
transformControls.addEventListener('dragging-changed', (event) => {
orbitControls.enabled = !event.value;
});ALWAYS listen to dragging-changed and disable camera controls while event.value is true.
---
Anti-Pattern 6: Not Adding TransformControls to the Scene
WRONG:
const transformControls = new TransformControls(camera, renderer.domElement);
transformControls.attach(cube);
// Forgot scene.add(transformControls) !What happens: The gizmo handles are invisible. The TransformControls object exists and processes events, but nothing renders on screen. The user cannot see or click the translate/rotate/scale handles.
CORRECT:
const transformControls = new TransformControls(camera, renderer.domElement);
scene.add(transformControls); // MUST add to scene for gizmo visibility
transformControls.attach(cube);---
Anti-Pattern 7: Calling PointerLockControls.lock() Without User Gesture
WRONG:
const controls = new PointerLockControls(camera, renderer.domElement);
controls.lock(); // Called immediately on page loadWhat happens: The browser silently rejects the pointer lock request. The Pointer Lock API requires a user-initiated event (click, keydown). The console shows no error, but the pointer is never locked and isLocked remains false.
CORRECT:
const controls = new PointerLockControls(camera, renderer.domElement);
renderer.domElement.addEventListener('click', () => {
controls.lock(); // ALWAYS from a user gesture
});---
Anti-Pattern 8: Omitting delta in FlyControls.update()
WRONG:
function animate() {
requestAnimationFrame(animate);
controls.update(); // Missing delta argument!
renderer.render(scene, camera);
}What happens: FlyControls.update() receives undefined as delta, resulting in NaN or zero movement. The camera either freezes completely or moves at an unpredictable speed depending on internal fallback behavior.
CORRECT:
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
controls.update(delta); // ALWAYS pass delta for FlyControls
renderer.render(scene, camera);
}---
Anti-Pattern 9: Setting target Without Calling update()
WRONG:
controls.target.set(10, 0, 5);
// Camera still looks at (0, 0, 0) until next user interactionWhat happens: Changing target only sets the value. The camera does NOT re-orient until update() is called. If enableDamping is false and the render loop does not call update(), the camera appears stuck at the old target.
CORRECT:
controls.target.set(10, 0, 5);
controls.update(); // ALWAYS call after programmatic target changes---
Anti-Pattern 10: Using OrbitControls with Orthographic Camera Zoom Properties on Perspective Camera
WRONG:
const camera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);
const controls = new OrbitControls(camera, renderer.domElement);
controls.minZoom = 0.5; // Has NO effect on PerspectiveCamera
controls.maxZoom = 5.0; // Has NO effect on PerspectiveCameraWhat happens: minZoom and maxZoom ONLY affect OrthographicCamera.zoom. For PerspectiveCamera, these properties are silently ignored. The camera zooms without any limits.
CORRECT:
// For PerspectiveCamera -- use distance limits
controls.minDistance = 2;
controls.maxDistance = 50;
// For OrthographicCamera -- use zoom limits
// controls.minZoom = 0.5;
// controls.maxZoom = 5.0;ALWAYS use minDistance/maxDistance for PerspectiveCamera and minZoom/maxZoom for OrthographicCamera. NEVER mix them up.
threejs-syntax-controls -- Examples
Example 1: OrbitControls with Damping
Standard setup for inspecting a 3D model with smooth camera movement.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 3, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Controls -- ALWAYS pass renderer.domElement
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.minDistance = 2;
controls.maxDistance = 20;
controls.maxPolarAngle = Math.PI / 2; // Prevent camera going below ground
controls.target.set(0, 1, 0); // Look at object center
// Add a mesh
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshStandardMaterial({ color: 0x0077ff });
const cube = new THREE.Mesh(geometry, material);
cube.position.set(0, 1, 0);
scene.add(cube);
// Light
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 1);
dirLight.position.set(5, 10, 5);
scene.add(dirLight);
// Animation loop -- MUST call controls.update() when enableDamping is true
function animate() {
requestAnimationFrame(animate);
controls.update(); // REQUIRED for damping
renderer.render(scene, camera);
}
animate();
// Cleanup on page unload
window.addEventListener('beforeunload', () => {
controls.dispose();
renderer.dispose();
});---
Example 2: PointerLockControls for First-Person
First-person camera with WASD movement. Pointer lock activates on click.
import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1.6, 5); // Eye height
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new PointerLockControls(camera, renderer.domElement);
// MUST activate from user gesture
document.addEventListener('click', () => {
controls.lock();
});
controls.addEventListener('lock', () => {
console.log('Pointer locked -- use mouse to look around');
});
controls.addEventListener('unlock', () => {
console.log('Pointer unlocked');
});
// Movement state
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
const moveState = { forward: false, backward: false, left: false, right: false };
document.addEventListener('keydown', (event) => {
switch (event.code) {
case 'KeyW': moveState.forward = true; break;
case 'KeyS': moveState.backward = true; break;
case 'KeyA': moveState.left = true; break;
case 'KeyD': moveState.right = true; break;
}
});
document.addEventListener('keyup', (event) => {
switch (event.code) {
case 'KeyW': moveState.forward = false; break;
case 'KeyS': moveState.backward = false; break;
case 'KeyA': moveState.left = false; break;
case 'KeyD': moveState.right = false; break;
}
});
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (controls.isLocked) {
// Deceleration
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
// Direction from input
direction.z = Number(moveState.forward) - Number(moveState.backward);
direction.x = Number(moveState.right) - Number(moveState.left);
direction.normalize();
const speed = 5.0;
if (moveState.forward || moveState.backward) velocity.z -= direction.z * speed * delta;
if (moveState.left || moveState.right) velocity.x -= direction.x * speed * delta;
controls.moveRight(-velocity.x * delta);
controls.moveForward(-velocity.z * delta);
}
renderer.render(scene, camera);
}
animate();---
Example 3: TransformControls with OrbitControls
Editor setup where you can orbit the scene AND move/rotate/scale objects with a gizmo.
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(5, 3, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// Orbit controls for camera
const orbitControls = new OrbitControls(camera, renderer.domElement);
orbitControls.enableDamping = true;
// Transform controls for object manipulation
const transformControls = new TransformControls(camera, renderer.domElement);
scene.add(transformControls); // MUST add to scene
// CRITICAL: Disable orbit while dragging the gizmo
transformControls.addEventListener('dragging-changed', (event) => {
orbitControls.enabled = !event.value;
});
// Create an object to manipulate
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0xff4444 })
);
scene.add(cube);
transformControls.attach(cube);
// Keyboard shortcuts for mode switching
document.addEventListener('keydown', (event) => {
switch (event.key) {
case 'g': transformControls.setMode('translate'); break;
case 'r': transformControls.setMode('rotate'); break;
case 's': transformControls.setMode('scale'); break;
case 'x': transformControls.showX = !transformControls.showX; break;
case 'y': transformControls.showY = !transformControls.showY; break;
case 'z': transformControls.showZ = !transformControls.showZ; break;
case ' ': // Toggle world/local space
transformControls.setSpace(
transformControls.space === 'world' ? 'local' : 'world'
);
break;
}
});
// Snapping with Ctrl held
transformControls.addEventListener('objectChange', () => {
// React to object transform changes
console.log('Object moved to:', cube.position.toArray());
});
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
scene.add(new THREE.DirectionalLight(0xffffff, 1).translateZ(5));
function animate() {
requestAnimationFrame(animate);
orbitControls.update();
renderer.render(scene, camera);
}
animate();
// Cleanup
function cleanup() {
transformControls.dispose();
orbitControls.dispose();
renderer.dispose();
}---
Example 4: FlyControls for Free Flight
Unrestricted camera movement with WASD and mouse.
import * as THREE from 'three';
import { FlyControls } from 'three/addons/controls/FlyControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
camera.position.set(0, 50, 200);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new FlyControls(camera, renderer.domElement);
controls.movementSpeed = 50;
controls.rollSpeed = 0.5;
controls.dragToLook = true; // Only rotate when dragging mouse
// Add some geometry to fly around
for (let i = 0; i < 200; i++) {
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(10, 10, 10),
new THREE.MeshNormalMaterial()
);
mesh.position.set(
Math.random() * 1000 - 500,
Math.random() * 1000 - 500,
Math.random() * 1000 - 500
);
scene.add(mesh);
}
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
controls.update(delta); // MUST pass delta -- NEVER omit
renderer.render(scene, camera);
}
animate();---
Example 5: MapControls for Top-Down View
Map-like navigation for architectural or strategy views.
import * as THREE from 'three';
import { MapControls } from 'three/addons/controls/MapControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 50, 50);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new MapControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxPolarAngle = Math.PI / 2.5; // Prevent camera going below horizon
controls.minDistance = 10;
controls.maxDistance = 200;
// Ground plane
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(200, 200),
new THREE.MeshStandardMaterial({ color: 0x228B22 })
);
ground.rotation.x = -Math.PI / 2;
scene.add(ground);
// Grid helper
scene.add(new THREE.GridHelper(200, 40, 0x000000, 0x444444));
// Buildings
for (let i = 0; i < 20; i++) {
const height = Math.random() * 10 + 2;
const building = new THREE.Mesh(
new THREE.BoxGeometry(4, height, 4),
new THREE.MeshStandardMaterial({ color: 0x888888 })
);
building.position.set(
Math.random() * 100 - 50,
height / 2,
Math.random() * 100 - 50
);
scene.add(building);
}
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const sun = new THREE.DirectionalLight(0xffffff, 0.8);
sun.position.set(50, 100, 50);
scene.add(sun);
function animate() {
requestAnimationFrame(animate);
controls.update(); // REQUIRED for damping
renderer.render(scene, camera);
}
animate();threejs-syntax-controls -- Methods Reference
OrbitControls
Import: import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
Constructor:
new OrbitControls(camera: THREE.Camera, domElement: HTMLElement)Properties (Complete)
| Property | Type | Default | Description |
|---|---|---|---|
autoRotate | boolean | false | Auto-rotate around target |
autoRotateSpeed | number | 2.0 | Rotation speed (degrees/sec at 60fps) |
cursor | string | 'auto' | CSS cursor style |
dampingFactor | number | 0.05 | Inertia factor (0-1), requires enableDamping |
domElement | HTMLElement | -- | Event listener target (read-only after construct) |
enabled | boolean | true | Enable/disable all interaction |
enableDamping | boolean | false | Smooth inertial movement |
enablePan | boolean | true | Allow panning |
enableRotate | boolean | true | Allow rotation |
enableZoom | boolean | true | Allow zooming |
keys | { LEFT: string, UP: string, RIGHT: string, BOTTOM: string } | Arrow key codes | Keyboard pan keys |
maxAzimuthAngle | number | Infinity | Max horizontal angle (radians) |
maxDistance | number | Infinity | Max dolly distance (PerspectiveCamera only) |
maxPolarAngle | number | Math.PI | Max vertical angle (radians) |
maxTargetRadius | number | Infinity | Max distance target can move from origin |
maxZoom | number | Infinity | Max zoom factor (OrthographicCamera only) |
minAzimuthAngle | number | -Infinity | Min horizontal angle (radians) |
minDistance | number | 0 | Min dolly distance (PerspectiveCamera only) |
minPolarAngle | number | 0 | Min vertical angle (radians) |
minTargetRadius | number | 0 | Min distance target can move from origin |
minZoom | number | 0 | Min zoom factor (OrthographicCamera only) |
mouseButtons | { LEFT: MOUSE, MIDDLE: MOUSE, RIGHT: MOUSE } | { LEFT: ROTATE, MIDDLE: DOLLY, RIGHT: PAN } | Mouse button mapping |
panSpeed | number | 1.0 | Pan speed multiplier |
rotateSpeed | number | 1.0 | Rotation speed multiplier |
screenSpacePanning | boolean | true | true: pan in screen plane. false: pan in horizontal plane |
target | THREE.Vector3 | (0, 0, 0) | Orbit focus point |
touches | { ONE: TOUCH, TWO: TOUCH } | { ONE: ROTATE, TWO: DOLLY_PAN } | Touch gesture mapping |
zoomSpeed | number | 1.0 | Zoom speed multiplier |
zoomToCursor | boolean | false | Zoom towards cursor position instead of target |
Methods (Complete)
update(deltaTime?: number): booleanUpdate controls. Returns true if camera position changed. MUST call every frame when enableDamping or autoRotate is true. The optional deltaTime parameter enables framerate-independent damping.
dispose(): voidRemove ALL event listeners from domElement. ALWAYS call on cleanup.
saveState(): voidSave the current camera position, target, and zoom as the reset state.
reset(): voidReset camera to the last saveState() values. If saveState() was never called, resets to construction-time values.
getDistance(): numberReturns the current distance from the camera to the target.
getPolarAngle(): numberReturns the current vertical angle in radians (0 = top, Math.PI = bottom).
getAzimuthalAngle(): numberReturns the current horizontal angle in radians.
listenToKeyEvents(domElement: HTMLElement): voidEnable keyboard-based panning using arrow keys. Pass document or a focusable element.
stopListenToKeyEvents(): voidDisable keyboard panning.
Events
| Event | Properties | Trigger |
|---|---|---|
change | none | Camera position or target changed |
start | none | User interaction began |
end | none | User interaction ended |
---
MapControls
Import: import { MapControls } from 'three/addons/controls/MapControls.js'
Constructor:
new MapControls(camera: THREE.Camera, domElement: HTMLElement)Subclass of OrbitControls. ALL properties and methods are inherited. The ONLY differences:
| Property | OrbitControls Default | MapControls Default |
|---|---|---|
mouseButtons.LEFT | ROTATE | PAN |
mouseButtons.RIGHT | PAN | ROTATE |
screenSpacePanning | true | true |
---
FlyControls
Import: import { FlyControls } from 'three/addons/controls/FlyControls.js'
Constructor:
new FlyControls(camera: THREE.Camera, domElement: HTMLElement)Properties
| Property | Type | Default | Description |
|---|---|---|---|
movementSpeed | number | 1.0 | Translation speed |
rollSpeed | number | 0.005 | Roll rotation speed |
dragToLook | boolean | false | true: mouse drag to rotate. false: mouse always rotates |
autoForward | boolean | false | Automatic forward movement |
Methods
update(delta: number): voidUpdate camera position/rotation. ALWAYS pass delta from clock.getDelta(). NEVER omit this argument.
dispose(): voidRemove all event listeners.
Keyboard Controls
| Key | Action |
|---|---|
| W | Move forward |
| S | Move backward |
| A | Move left |
| D | Move right |
| R | Move up |
| F | Move down |
| Q | Roll left |
| E | Roll right |
---
PointerLockControls
Import: import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'
Constructor:
new PointerLockControls(camera: THREE.Camera, domElement: HTMLElement)Properties
| Property | Type | Default | Description |
|---|---|---|---|
isLocked | boolean | false (read-only) | Whether pointer is currently locked |
maxPolarAngle | number | Math.PI | Max vertical look angle (radians) |
minPolarAngle | number | 0 | Min vertical look angle (radians) |
pointerSpeed | number | 1.0 | Mouse sensitivity multiplier |
Methods
lock(): voidRequest pointer lock from the browser. MUST be called from a user gesture (click handler). Browsers reject unprompted lock requests.
unlock(): voidExit pointer lock.
connect(): voidAttach pointer lock event listeners to domElement.
disconnect(): voidRemove pointer lock event listeners.
dispose(): voidFull cleanup. Calls disconnect() internally.
getObject(): THREE.CameraReturns the controlled camera.
getDirection(target: THREE.Vector3): THREE.Vector3Write the camera's look direction into target and return it.
moveForward(distance: number): voidMove camera forward along the XZ plane (y stays constant).
moveRight(distance: number): voidMove camera sideways along the XZ plane.
Events
| Event | Trigger |
|---|---|
change | Camera orientation changed |
lock | Pointer lock acquired |
unlock | Pointer lock released |
---
TransformControls
Import: import { TransformControls } from 'three/addons/controls/TransformControls.js'
Constructor:
new TransformControls(camera: THREE.Camera, domElement: HTMLElement)Properties
| Property | Type | Default | Description |
|---|---|---|---|
axis | `string \ | null` | null |
dragging | boolean | false (read-only) | Whether user is currently dragging |
enabled | boolean | true | Enable/disable interaction |
mode | string | 'translate' | 'translate', 'rotate', or 'scale' |
object | THREE.Object3D | undefined | Currently attached object |
showX | boolean | true | Show X axis gizmo |
showY | boolean | true | Show Y axis gizmo |
showZ | boolean | true | Show Z axis gizmo |
size | number | 1 | Gizmo visual scale |
space | string | 'world' | 'world' or 'local' coordinate space |
translationSnap | `number \ | null` | null |
rotationSnap | `number \ | null` | null |
scaleSnap | `number \ | null` | null |
Methods
attach(object: THREE.Object3D): TransformControlsAttach the gizmo to an object. Returns this for chaining.
detach(): TransformControlsRemove the gizmo from the current object. Returns this.
setMode(mode: string): voidSet the transform mode: 'translate', 'rotate', or 'scale'.
setSpace(space: string): voidSet coordinate space: 'world' or 'local'. NOTE: 'local' has no effect in 'scale' mode -- scale ALWAYS operates in local space.
setSize(size: number): voidSet the gizmo visual scale.
setTranslationSnap(snap: number | null): voidSet position snapping. Pass null to disable.
setRotationSnap(snap: number | null): voidSet rotation snapping. Pass null to disable.
setScaleSnap(snap: number | null): voidSet scale snapping. Pass null to disable.
getRaycaster(): THREE.RaycasterReturns the internal raycaster for custom intersection logic.
getMode(): stringReturns the current mode.
dispose(): voidRemove all event listeners and clean up.
Events
| Event | Properties | Trigger |
|---|---|---|
change | none | Gizmo visual state changed |
dragging-changed | event.value: boolean | Drag started (true) or ended (false) |
objectChange | none | Attached object's transform was modified by the gizmo |
mouseDown | none | Pointer pressed on gizmo handle |
mouseUp | none | Pointer released from gizmo handle |
---
ArcballControls
Import: import { ArcballControls } from 'three/addons/controls/ArcballControls.js'
Constructor:
new ArcballControls(camera: THREE.Camera, domElement: HTMLElement, scene?: THREE.Scene)Advanced trackball rotation with no polar constraint. Supports animation states and cursor-based rotation scaling. The optional scene parameter enables focus animations.
Key methods: update(), dispose(), setGizmosVisible(visible), reset().
---
TrackballControls
Import: import { TrackballControls } from 'three/addons/controls/TrackballControls.js'
Constructor:
new TrackballControls(camera: THREE.Camera, domElement: HTMLElement)Properties
| Property | Type | Default | Description |
|---|---|---|---|
rotateSpeed | number | 1.0 | Rotation speed |
zoomSpeed | number | 1.2 | Zoom speed |
panSpeed | number | 0.3 | Pan speed |
noRotate | boolean | false | Disable rotation |
noZoom | boolean | false | Disable zooming |
noPan | boolean | false | Disable panning |
staticMoving | boolean | false | No inertia when true |
dynamicDampingFactor | number | 0.2 | Inertia factor |
Key methods: update(), dispose(), reset().
---
DragControls
Import: import { DragControls } from 'three/addons/controls/DragControls.js'
Constructor:
new DragControls(objects: THREE.Object3D[], camera: THREE.Camera, domElement: HTMLElement)Events
| Event | Trigger |
|---|---|
dragstart | Drag started on an object |
drag | Object is being dragged |
dragend | Drag ended |
hoveron | Pointer entered an object |
hoveroff | Pointer left an object |
Key methods: activate(), deactivate(), dispose(), getObjects(), getRaycaster().