**Note** from Bead: 3D Mouse Keyboard Camera Controls · [canonical source](https://redfish.acequia.io/guerin/.agents/b28561b2-f9e6-4a2a-88f3-edb06fb262e9/2026-06-17/notes/01-threejs-controls.md) · session 2026-06-17 · discussion: Talk: 3D Mouse Keyboard Camera Controls
three.js ships its controls as **addons** (`three/examples/jsm/controls/`, or in recent versions `three/addons/controls/`), not in core. They split into two families three.js treats as conceptually distinct: - **Camera-navigation controls** (move / orient the camera): `OrbitControls`, `MapControls`, `TrackballControls`, `ArcballControls`, `FlyControls`, `FirstPersonControls`, `PointerLockControls`. - **Object-manipulation controls** (leave the camera alone, move scene objects): `TransformControls` (gizmo edit) and `DragControls` (drag-and-drop). These are editor / authoring tools, paradigm (c). Cross-cutting facts (verified against the GitHub source, since the rendered doc pages are SPA-rendered): - The navigation controls except Fly / FirstPerson / PointerLock derive from a common `Controls` base and dispatch `'change'`, `'start'`, `'end'` events. - `OrbitControls`, `MapControls`, `TrackballControls`, and the time-based movers (`Fly` / `FirstPerson`) must have `update()` called every frame (mandatory when damping / inertia is on; Fly / FirstPerson need the frame `delta`). - `dispose()` removes all DOM event listeners and is the basis of runtime switching.
## 1. OrbitControls **Metaphor:** orbit a fixed `controls.target`; camera stays on a sphere around it, like a turntable. Keeps an up-vector so the horizon never tilts. - **Mouse (`.mouseButtons` defaults):** LEFT = `ROTATE` (orbit), MIDDLE = `DOLLY` (zoom; wheel also zooms), RIGHT = `PAN`. - **Touch (`.touches`):** ONE = `ROTATE`, TWO = `DOLLY_PAN`. - **Keyboard (`.keys`, off until `listenToKeyEvents(window)`):** W/A/S/D pan (arrow codes also supported). - **Up-vector:** constrained; honors `camera.up`, clamps pitch via `minPolarAngle` (0) / `maxPolarAngle` (pi); azimuth clampable; no roll, never flips over the poles. - **Key props:** `enableDamping` / `dampingFactor` (0.05), `enableZoom` / `enablePan` / `enableRotate`, `autoRotate`, `min/maxDistance`, `min/maxZoom`, `screenSpacePanning` (default **true**). - **Use case:** the default for model / product viewers, configurators, most general scenes. - Doc: https://threejs.org/docs/#examples/en/controls/OrbitControls
## 2. MapControls **Metaphor:** same engine as OrbitControls, retuned for top-down map / terrain panning (Google-Maps style): primary drag slides the ground plane instead of orbiting. - **Mouse (defaults; note the LEFT/RIGHT swap vs Orbit):** LEFT = `PAN`, MIDDLE = `DOLLY`, RIGHT = `ROTATE`. - **Touch:** ONE = `PAN`, TWO = `DOLLY_ROTATE`. - **Up-vector:** constrained like Orbit, and crucially `screenSpacePanning` defaults to **false**, so panning is along the ground plane (orthogonal to world `camera.up`), which is what makes it feel like a map. - **Use case:** GIS / map navigation, RTS / strategy cameras, architectural bird's-eye. - Accuracy note: MapControls is not just OrbitControls with a flag. Its default `mouseButtons` LEFT/RIGHT are swapped and `screenSpacePanning` defaults to false. - Doc: https://threejs.org/docs/#examples/en/controls/MapControls
## 3. TrackballControls **Metaphor:** virtual trackball; the scene sits inside a sphere you spin freely. - **Mouse:** LEFT = ROTATE, MIDDLE = DOLLY, RIGHT = PAN; wheel zooms. - **Keyboard (`.keys`):** `['KeyA','KeyS','KeyD']` force rotate / zoom / pan respectively (modifiers to lock an interaction mode, not WASD movement). - **Up-vector: none. This is the defining trait.** It does not keep a constant up; rotating over the poles does not flip right-side-up, and **roll is allowed** (`noRoll` can disable it). Full free rotation. - **Key props:** `rotateSpeed` 1.0, `zoomSpeed` 1.2, `panSpeed` 0.3, `staticMoving` false, `dynamicDampingFactor` 0.2, `noRotate/noZoom/noPan/noRoll`. Requires `update()` each frame. Caveat: does not work well with non-fullscreen / offset canvases. - **Use case:** scientific / data viz, molecular / CAD viewers where unconstrained orientation matters more than a stable horizon. - Doc: https://threejs.org/docs/#examples/en/controls/TrackballControls
## 4. ArcballControls **Metaphor:** trackball with an on-screen gizmo. Draws three colored rotation circles (red X / green Y / blue Z) into the scene that show and let you grab the virtual sphere. - **Mouse (default ops, reconfigurable via `setMouseAction()`):** left-drag rotate, right-drag pan, middle-drag zoom, wheel zoom, **Shift+wheel adjusts FOV** (vertigo / dolly-zoom). - **Up-vector:** free rotation, no fixed up-vector, like a trackball. Adds `cursorZoom`, `focus()` (double-tap to animate a point to center + zoom), `enableAnimations` (inertial spin + smooth focus, `dampingFactor`), `scaleFactor` (1.1), `adjustNearFar`, `setGizmosVisible()`. - **Use case:** high-fidelity model inspection wanting trackball freedom plus visible, discoverable rotation affordances and cinematic focus. - Doc: https://threejs.org/docs/#examples/en/controls/ArcballControls
## 5. FlyControls **Metaphor:** free 6-DOF flight; a spaceship / flight-sim camera. Pitch, yaw, and **roll** all available. - **Keyboard:** W/S forward/back, A/D strafe, R/F up/down, arrow keys pitch (Up/Down) + yaw (Left/Right), Q/E roll, Shift slows to ~0.1x. - **Mouse:** `dragToLook = false` (default) means the mouse continuously steers look direction, left-click = move forward, right-click = move backward. `dragToLook = true` means you only look while dragging. - **Up-vector: none**; full 6-DOF, roll permitted. - **Key props:** `movementSpeed` 1.0, `rollSpeed` 0.005, `dragToLook` false, `autoForward` false. **Requires `update(delta)`** each frame (movement is time-based). - **Use case:** space scenes, flythroughs, freeform exploration. - Doc: https://threejs.org/docs/#examples/en/controls/FlyControls
## 6. FirstPersonControls **Metaphor:** mouse-look + keyboard walk, like an old-school FPS / flythrough, **without** Pointer Lock capture (cursor stays visible). - **Keyboard:** W/Up forward, S/Down back, A/Left left, D/Right right, R up, F down. - **Mouse look:** in the current source, looking happens only while a mouse button is held (`mouseDragOn` gate). Older versions / docs exposed an `activeLook` flag that is gone in the current implementation; look is now effectively hold-to-look. - **Up-vector:** constrained, yaw + pitch only, no roll. `lookVertical` toggles pitch; `constrainVertical` + `verticalMin/Max` clamp it. Optional terrain-follow via `heightSpeed`/`heightCoef`/`heightMin/Max`. - **Key props:** `movementSpeed` 1.0, `lookSpeed` 0.005, `lookVertical` true, `autoForward` false, `constrainVertical` false. **Requires `update(delta)`** each frame. - **Use case:** architectural walkthroughs, simple terrain explorers where you do not want to capture the pointer. - Doc: https://threejs.org/docs/#examples/en/controls/FirstPersonControls
## 7. PointerLockControls **Metaphor:** true FPS pointer capture via the browser Pointer Lock API; cursor hidden / captured, every mouse movement turns the view (no button needed). - **Mouse:** raw `movementX/Y` drives yaw / pitch once locked; `pointerSpeed` (1.0) scales sensitivity. - **Keyboard: none built in, by design.** The developer wires their own keydown/keyup handlers and calls `moveForward(distance)` / `moveRight(distance)`. - **API:** `lock()` / `unlock()`, `isLocked`, events `'lock'` / `'unlock'` / `'change'`, `getDirection(v)`. Typically `lock()` is called from a click on a "Play" overlay (Pointer Lock requires a user gesture). - **Up-vector:** constrained, `'YXZ'` Euler order (yaw + pitch only), no roll ever, pitch clamped by `minPolarAngle` / `maxPolarAngle`. - **Use case:** first-person games / immersive FPS in the browser. - Doc: https://threejs.org/docs/#examples/en/controls/PointerLockControls
## 8. TransformControls — OBJECT EDIT, not camera nav Moves a scene object, not the camera. Attaches an interactive gizmo to a target `Object3D`. - **Modes:** `setMode('translate' | 'rotate' | 'scale')`; drag colored axis arrows / rings / handles (X red, Y green, Z blue, plus plane handles). - **Attach:** `attach(object)` / `detach()`. **Space:** `setSpace('local' | 'world')`. - **Snapping:** `setTranslationSnap()`, `setRotationSnap()`, `setScaleSnap()`. - **Critical integration:** it fires `'dragging-changed'`; use it to disable camera controls while dragging the gizmo so the two do not fight: ```js transformControls.addEventListener('dragging-changed', e => { orbitControls.enabled = !e.value; }); ``` - **Use case:** scene editors, level / asset authoring (the three.js editor itself). - Doc: https://threejs.org/docs/#examples/en/controls/TransformControls
## 9. DragControls — OBJECT EDIT, not camera nav Click-and-drag-to-reposition via raycasting. - **Construction:** `new DragControls(objects[], camera, domElement)`. - **Events:** `hoveron` / `hoveroff` / `dragstart` / `drag` / `dragend` (use dragstart/dragend to disable OrbitControls during a drag, same conflict-avoidance idea as TransformControls). - **Props:** `objects`, `raycaster`, `recursive`, `transformGroup`, `rotateSpeed`. - Doc: https://threejs.org/docs/#examples/en/controls/DragControls
## Comparison table | Control | Metaphor | Mouse (L / M / R) | Keyboard | Up-vector | |---|---|---|---|---| | **OrbitControls** | Orbit a target | Rotate / Dolly / Pan | WASD pan (opt-in) | Fixed (no roll, pitch-clamped) | | **MapControls** | Top-down map pan | **Pan / Dolly / Rotate** | WASD pan (opt-in) | Fixed; `screenSpacePanning=false` | | **TrackballControls** | Virtual trackball | Rotate / Dolly / Pan | A/S/D = mode lock | **None, free, roll allowed** | | **ArcballControls** | Trackball + visible gizmo | Rotate / Zoom / Pan (Shift+wheel = FOV) | -- | **None, free rotation** | | **FlyControls** | 6-DOF flight | L=fwd / -- / R=back; move=look | WASD + RF + arrows + QE(roll) | **None, full 6-DOF, roll** | | **FirstPersonControls** | Mouse-look + walk (cursor visible) | Hold-drag to look | WASD/arrows + R/F | Fixed (yaw/pitch, no roll) | | **PointerLockControls** | FPS pointer capture | Captured move = look | **None built in** (dev wires WASD) | Fixed (YXZ, no roll, pitch-clamped) | | **TransformControls** | *Object* gizmo edit | Drag axis handles | -- | n/a (edits object) | | **DragControls** | *Object* drag-drop | Drag to move object | -- | n/a (edits object) |
## Switching controls at runtime There is no built-in "controls manager"; the canonical pattern is **dispose + recreate**, with manual camera-state preservation. 1. **Dispose old, instantiate new.** `dispose()` detaches all DOM listeners; without it you get duplicate listeners and fighting controls. ```js function setControls(Kind) { if (controls) controls.dispose(); // remove old listeners controls = new Kind(camera, renderer.domElement); controls.target?.copy(lastTarget); // restore where applicable controls.update?.(); } ``` 2. **Preserve camera state.** Different controls model orientation differently, so swapping (e.g. Orbit -> Fly -> back) makes the camera jump unless you carry over `camera.position`, `camera.quaternion`, and `controls.target` across the swap. Well-known forum gotcha. 3. **The render-loop contract differs by control:** - Damped / inertial (Orbit, Map with `enableDamping`, Trackball, Arcball with `enableAnimations`): `controls.update()` every frame. - Time-based movers (Fly, FirstPerson): `controls.update(delta)` with the frame delta from `THREE.Clock`. - PointerLock: no per-frame `update()` for looking; your own movement code reads key state each frame and calls `moveForward` / `moveRight`. - Event-driven render (no animation loop, static scenes, damping off): `controls.addEventListener('change', () => renderer.render(scene, camera));` 4. **Damping** is the smoothing / inertia layer (`enableDamping` + `dampingFactor`; Trackball `staticMoving=false` + `dynamicDampingFactor`; Arcball `enableAnimations` + `dampingFactor`). Any of these mandates the per-frame `update()`. 5. **Nav vs object controls coexist, do not swap, gate them.** TransformControls / DragControls are added alongside a camera control. Disable the camera control during an object drag via `'dragging-changed'` (Transform) or `'dragstart'/'dragend'` (Drag).
## Sources - OrbitControls https://threejs.org/docs/#examples/en/controls/OrbitControls - MapControls https://threejs.org/docs/#examples/en/controls/MapControls - TrackballControls https://threejs.org/docs/#examples/en/controls/TrackballControls - ArcballControls https://threejs.org/docs/#examples/en/controls/ArcballControls - FlyControls https://threejs.org/docs/#examples/en/controls/FlyControls - FirstPersonControls https://threejs.org/docs/#examples/en/controls/FirstPersonControls - PointerLockControls https://threejs.org/docs/#examples/en/controls/PointerLockControls - TransformControls https://threejs.org/docs/#examples/en/controls/TransformControls - DragControls https://threejs.org/docs/#examples/en/controls/DragControls - Source (authoritative defaults) https://github.com/mrdoob/three.js/tree/dev/examples/jsm/controls - Forum, full dispose of OrbitControls https://discourse.threejs.org/t/is-there-a-way-to-full-dispose-orbitcontrols/26617 - Forum, switching controllers (camera-jump caveat) https://discourse.threejs.org/t/switch-controllers/54348