Command Palette

Search for a command to run...

Hectal
Course outline
Topic 7.3Intermediate ~20 min · step 3 of 6

OrbitControls

In one line

OrbitControls lets a visitor orbit, zoom and (optionally) pan the camera around a target with mouse, touch or wheel. Left alone it lets the camera go under the floor and through the bike, and it takes over touch scrolling, so you set limits and know what it claims.

0/6 · 0%

Think of it like this

A photographer's tripod with a ball joint. The camera always looks at the subject. You can swing it around, tilt it up and down, or slide the whole tripod closer or farther. OrbitControls is that tripod, and the subject is its target.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

target
The point the camera looks at and orbits around, a Vector3.
polar angle
The camera's angle from straight up: 0 looks down from above, π / 2 is the horizon, π is from below.
damping
Letting a motion fade out instead of stopping at once, like a spinning wheel slowing down.
dolly
Moving the camera closer to or farther from the target (zooming by moving).
pan
Sliding the camera and its target sideways together.
touch-action
A CSS property that tells the browser which touch gestures it may handle itself, such as scrolling. none hands all of them to your code.

Step by step

01Run the controls

Drag the first example with the mouse or a finger, scroll to zoom. The camera orbits the middle of the "frame" at 0.45 m, because the target is set there.

02Compare limits with no limits

The same example pushes two controls to the extreme: the camera dragged 115° down and a zoom of 100×. Default controls end at a camera height of −2.60 m (under the floor) and 0.03 m from the target. Limited controls stop at 0.64 m and 0.80 m.

03See what the controls claim

The second example sends a wheel event and reads the canvas's touch-action at five moments: before the controls, after creating them, with enableZoom = false, after setting pan-y, and after dispose().

04Keep update() in the loop

With enableDamping = true, a drag only starts the motion; each controls.update() call applies the next share of it. So the line controls.update() goes inside the animation loop, before renderer.render(scene, camera). Remove it and the camera still turns while you drag, but stops dead when you let go, because nothing applies the remaining motion.

Try it yourself

  1. 1

    Change a limit

    In the first example set minDistance to 2: the limited line ends with closest distance 2.00 m. Set maxPolarAngle to Math.PI / 2 instead: the camera height becomes 0.45 m, exactly the target's height, because the camera sits on the horizon.

  2. 2

    Lock the tilt

    Replace the maxPolarAngle line with controls.minPolarAngle = controls.maxPolarAngle = Math.PI / 2.5;. The same 115° downward drag now ends at a camera height of 1.39 m: the tilt is frozen at 72° from straight up and only the sideways turn and the zoom still work.

Code & diagrams

OrbitControls with limits New tab

Drag, scroll or pinch to try it. The two lines come from a test that pushes both setups as hard as a user could. `dollyOut(100)` shrinks the distance, and `rotateUp(-2)` drags the camera downwards.

idle

Console

Run the scene to see its console.

Expected console output

default limits: camera height -2.60 m; closest distance 0.03 m
with limits   : camera height 0.64 m; closest distance 0.80 m
What the controls take from the page New tab

The WheelEvent is sent by the code itself. In a real page the same applies to a person's wheel, and the touch-action line decides what a finger does.

idle

Console

Run the scene to see its console.

Expected console output

before the controls: touch-action = "" | wheel scrolls the page: yes
controls made:       touch-action = "none" | wheel scrolls the page: no (the controls took it)
enableZoom = false:  touch-action = "none" | wheel scrolls the page: yes
pan-y set by us:     touch-action = "pan-y" | wheel scrolls the page: no (the controls took it)
after dispose():     touch-action = "" | wheel scrolls the page: yes

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Forget update() with damping

In the first example, delete the line controls.update(); inside setAnimationLoop.

terminal
$ Run, then drag
── what you'll see ──
no error: the picture stays still and does not react to a drag or the wheel

Myth vs fact

Myth

OrbitControls rotates the object.

Fact

It moves the camera around a target. The bike never turns, so lights fixed in the world stay where they are and the reflections change, which is what a visitor walking around a real bike would see.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    update() returns true when the camera moved since the last call. Together with the change event this is the hook for rendering only when something changed, which saves battery (Phase 17).

  • ▸

    minTargetRadius and maxTargetRadius limit how far the target may be panned from controls.cursor. That allows a small pan without letting a visitor lose the bike.

Remember this

  1. 1

    The problem this solves: turning a bike with the mouse needs pointer events, angles, damping and touch gestures, and every part is hard to get right. new OrbitControls(camera, renderer.domElement) does all of it. It lives in the add-ons: import { OrbitControls } from "three/addons/controls/OrbitControls.js".

  2. 2

    How it works. The controls keep the camera on a sphere around target: a distance, a polar angle from straight up (0 to π) and an azimuth around the vertical. A drag changes the angles, the wheel the distance, a pan moves the target; update() writes the position and aims the camera. Mouse: left orbits, wheel zooms, right pans. Touch: one finger orbits, two pinch and pan. Arrow keys need controls.listenToKeyEvents(window).

  3. 3

    Target. The default target is (0, 0, 0), the stand's floor. The bike's middle is about 0.45 m up, so set controls.target.set(0, 0.45, 0) and call update(). Without it the bike swings around a point under its frame.

  4. 4

    **Damping needs update().** enableDamping = true makes the motion ease out, but only if controls.update() runs every frame. The damping factor (default 0.05) is a share applied per update() call, not per second. After a 57° flick, 1 update applies 2.9°, 30 updates 45° and 90 updates 99%: 1.5 s on a 60 Hz screen but 0.63 s on a 144 Hz screen.

  5. 5

    Limits are not optional. Defaults allow any distance and any polar angle from 0 to π, so a visitor can drag the camera below the target, under the floor, or zoom through the bike. Set minDistance and maxDistance, and maxPolarAngle (π / 2 is the horizon: with a target above the floor the camera then never goes below it). minPolarAngle, minAzimuthAngle and maxAzimuthAngle limit the other directions. enablePan = false keeps the bike centred.

  6. 6

    It claims the touch screen. The constructor sets touch-action: none on the canvas, so every finger movement goes to the controls and the page never scrolls; the wheel is taken with preventDefault() too. On a full-screen canvas that is right. On a scrolling page a full-width canvas traps the user's finger: leave room outside it, or set canvas.style.touchAction = "pan-y" so vertical swipes scroll the page (one finger then orbits only sideways).

  7. 7

    Clean up. controls.dispose() removes the listeners and restores touch-action. The controls also fire start, change and end events, which the next topics use.

Explain it without notes

01

A visitor on a phone says the page will not scroll when the finger is on the 3D view. What causes it and what are two fixes?

02

Why can a camera end up under the floor with default OrbitControls, and what one setting prevents it?

Practice

01

Make the controls so the bike can only turn sideways (no tilt), between 1 m and 4 m away, with no panning.

02

Print a line each time the user starts and stops moving the camera.

Done when you can

  • Create OrbitControls, set its target and call update() in the loop

  • Set limits so the camera cannot go under the floor or through the bike

  • Explain damping in terms of update() calls

  • Say what the controls take from the page (touch, wheel) and how to give it back