The add-ons ship eight more controls, all constructed with a camera (or objects) and an element:
| Control | Behavior | Good for |
|---|---|---|
| MapControls | Orbit with pan on left-drag | Maps, floor plans, top-down views |
| TrackballControls | Free rotation, no fixed up | Molecules, CAD parts |
| ArcballControls | Virtual trackball with gizmos | Inspecting models |
| FlyControls | Six degrees of freedom | Space and flight scenes |
| FirstPersonControls | Look with the mouse, move with keys | Walkthrough previews |
| PointerLockControls | Mouse-look with a locked pointer | First-person games |
| TransformControls | Move, rotate or scale gizmo | Editors, placing objects |
| DragControls | Drag objects on a plane | Arranging books (Dragging Objects in 3D) |
Outside the add-ons, camera-controls 2,430 (github.com/yomotsu/camera-controls (https://github.com/yomotsu/camera-controls 2,430 ), MIT, npm 2,036 install camera-controls) adds smooth animated transitions such as fitToBox() and setLookAt(), and drei 9,900 wraps it for React 7,897 (React Three Fiber and drei). Babylon.js 89,603 builds these behaviors into its cameras instead; Babylon.js's ArcRotateCamera is the counterpart of OrbitControls.
<!doctype html>
<style>
body { margin: 0; background: #f5f1e8; font: 13px system-ui, sans-serif; color: #263238; }
canvas { display: block; max-width: 100%; }
.ui { padding: 4px 8px; }
</style>
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="ui">Gizmo: <button data-mode="translate">translate</button><button data-mode="rotate">rotate</button><button data-mode="scale">scale</button>
· Camera: <select id="cam"><option>OrbitControls</option><option>MapControls</option></select></div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { MapControls } from 'three/addons/controls/MapControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 300);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('#fbf8f2');
const camera = new THREE.PerspectiveCamera(40, 2, 0.1, 50);
camera.position.set(1.6, 1.6, 3);
scene.add(new THREE.HemisphereLight('#ffffff', '#8d6e63', 1.2));
const sun = new THREE.DirectionalLight('#ffffff', 2.5);
sun.position.set(2, 3, 4);
scene.add(sun, new THREE.GridHelper(6, 12, '#b0a898', '#d8d0c0'));
const books = ['#1f5f8b', '#e09a10', '#b5452f'].map((color, i) => {
const book = new THREE.Mesh(new THREE.BoxGeometry(0.42, 0.98, 0.12).translate(0, 0.49, 0),
new THREE.MeshStandardMaterial({ color }));
book.position.x = (i - 1) * 0.8;
scene.add(book);
return book;
});
let cameraControls = new OrbitControls(camera, renderer.domElement);
// TransformControls: a move / rotate / scale gizmo, the tool of editors
const gizmo = new TransformControls(camera, renderer.domElement);
gizmo.attach(books[1]);
scene.add(gizmo.getHelper()); // r169+: the helper is what gets drawn
// Don't orbit while a gizmo handle is being dragged
gizmo.addEventListener('dragging-changed', (e) => { cameraControls.enabled = !e.value; });
document.querySelectorAll('[data-mode]').forEach((b) => b.onclick = () => gizmo.setMode(b.dataset.mode));
document.getElementById('cam').addEventListener('change', (e) => {
cameraControls.dispose(); // detach the old listeners
const Controls = e.target.value === 'MapControls' ? MapControls : OrbitControls;
cameraControls = new Controls(camera, renderer.domElement); // MapControls: left-drag pans
});
renderer.setAnimationLoop(() => {
cameraControls.update();
renderer.render(scene, camera);
});
</script>