Listen up. If you're building modern applications, understanding Camera Controls in Three.js 3D WebGL is non-negotiable. This is where simple logic turns into intelligent behavior.
1Locked Camera vs Interactive Camera
So far, our camera has been locked in place. In a 3D world, the user expects to be able to look around, zoom, and explore ā a static viewpoint feels closer to a screenshot than an actual 3D experience.
Making a camera interactive means responding to mouse or touch input by adjusting its position and rotation every frame, while still respecting the same Scene/Camera/Renderer pipeline you've already learned. Controls don't replace the camera; they simply drive it based on user input.
// š® Taking control of the camera3D Scene rendered. Objects: 4, Draw Calls: Optimized.
2OrbitControls: Built-in Camera Interaction
You could write complex math using mouse events to move the camera, but Three.js provides built-in 'Controls' classes to do this for you, saving you from reimplementing trigonometry that's already been solved and battle-tested.
OrbitControls is the most commonly used of these ā it's shipped as an official Three.js example add-on rather than part of the core library, which is why it's imported from a separate 'examples/jsm/controls' path instead of the main three package.
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';3D Scene rendered. Objects: 4, Draw Calls: Optimized.
3How OrbitControls Handles Mouse Input
OrbitControls allow you to left-click and drag to rotate, right-click and drag to pan, and scroll to zoom. It orbits around a 'target' point (which is [0,0,0] by default) rather than rotating the camera in place around its own axis.
This target-based orbiting is what makes it feel natural for inspecting an object: the camera always stays pointed at the target as it moves, so dragging never accidentally spins the view away from what you're looking at. You can move the target itself if you want the orbit to center on a different point in the scene.
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // Adds smooth physics!3D Scene rendered. Objects: 4, Draw Calls: Optimized.
4Damping and controls.update()
If you enable 'damping' (smooth inertia), you MUST call controls.update() inside your animation loop, otherwise it won't work ā damping simulates momentum by gradually easing the camera toward its target position across multiple frames instead of snapping instantly.
This is one of the most common OrbitControls gotchas in vanilla Three.js: enabling enableDamping without also calling update() every frame results in controls that appear completely frozen, since the eased movement never actually gets applied without that per-frame recalculation.
function animate() {
requestAnimationFrame(animate);
controls.update(); // Required if damping is enabled
renderer.render(scene, camera);
}3D Scene rendered. Objects: 4, Draw Calls: Optimized.
5What update() Actually Recalculates
If you set controls.enableDamping = true to get smooth, buttery camera movements, calling controls.update() each frame is what recalculates the camera's eased position based on however much 'inertia' remains from the last drag or scroll input.
Even without damping enabled, it's good practice to call controls.update() every frame anyway ā some other OrbitControls features (like auto-rotation) also depend on this per-frame recalculation, so making it a habitual part of your render loop avoids subtle bugs later when you enable those features.
function animate() {
???
}3D Scene rendered. Objects: 4, Draw Calls: Optimized.
6OrbitControls in React Three Fiber
In React Three Fiber, this is hilariously easy. You just import OrbitControls from @react-three/drei and drop it in your Canvas ā no manual instantiation, no renderer.domElement plumbing, and no manual controls.update() call needed.
drei's OrbitControls component wires all of that up internally, including the per-frame update() call, damping defaults, and disposal when the component unmounts. This is a good example of how drei exists specifically to wrap common vanilla Three.js add-ons into zero-boilerplate React components.
import { OrbitControls } from '@react-three/drei';
<Canvas>
<OrbitControls />
<mesh />
</Canvas>3D Scene rendered. Objects: 4, Draw Calls: Optimized.
7Trying Out Interactive Orbiting
Try it out! Click and drag on the 3D preview below to rotate the camera around the torus knot. Scroll to zoom in and out ā this hands-on feedback loop is exactly what OrbitControls is built to provide with almost no setup code.
The makeDefault prop used here tells R3F to register this specific controls instance as the scene's default controls, which matters if other components (like transform gizmos) need to know which controls to temporarily disable during their own interactions.
import { OrbitControls } from '@react-three/drei';
const App = () => {
return (
<Canvas camera={{ position: [0, 0, 5] }}>
<OrbitControls makeDefault autoRotate />
<ambientLight intensity={0.5} />
<directionalLight position={[10, 10, 10]} intensity={2} />
<mesh>
<torusKnotGeometry args={[1, 0.3, 128, 16]} />
<meshStandardMaterial color="#00F0FF" roughness={0.1} metalness={0.8} />
</mesh>
</Canvas>
);
};
render(<App />);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
8autoRotate for Product Showcases
Notice that we passed autoRotate to the OrbitControls. This automatically spins the camera around the object slowly, which looks incredibly premium for product showcases ā it keeps the scene visually alive even when no one is actively interacting with it.
autoRotate typically pauses automatically the moment the user starts dragging, and resumes after they let go, which is why it feels natural rather than fighting against user input. autoRotateSpeed controls how fast that idle rotation spins.
<OrbitControls autoRotate autoRotateSpeed={2} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
9Restricting Camera Movement
You can also restrict the controls. For example, stopping the user from zooming too far in or panning the camera under the floor ā unrestricted OrbitControls can let users clip through geometry or lose the object entirely.
minDistance and maxDistance bound how far the camera can zoom in or out, while maxPolarAngle limits vertical rotation (Math.PI / 2 stops the camera from dropping below a horizontal 'ground level' view). These constraints are essential for product showcases and architectural walkthroughs where certain angles would break the illusion or reveal unfinished geometry.
<OrbitControls
minDistance={2}
maxDistance={10}
maxPolarAngle={Math.PI / 2} // Can't go below ground
/>3D Scene rendered. Objects: 4, Draw Calls: Optimized.
10Wrapping Up Camera Controls
Awesome! You've given the user agency in your 3D world. They can now look at your masterpieces from any angle, with sensible boundaries keeping the experience polished rather than breakable.
From here, the same interaction patterns extend to more specialized control schemes ā FirstPersonControls for walkthroughs, PointerLockControls for FPS-style games, or fully custom drag handlers when you need behavior OrbitControls doesn't cover out of the box.
// š® Controls activated!3D Scene rendered. Objects: 4, Draw Calls: Optimized.
11Step-by-Step Breakdown
So far, our camera has been locked in place. In a 3D world, the user expects to be able to look around, zoom, and explore.
You could write complex math using mouse events to move the camera, but Three.js provides built-in 'Controls' to do this for you.
Which Three.js add-on class allows the user to orbit (rotate) the camera around a specific target using the mouse?
- āMouseControls
- āOrbitControls
- āCameraControls
OrbitControls allow you to left-click and drag to rotate, right-click and drag to pan, and scroll to zoom. It orbits around a 'target' (which is [0,0,0] by default).
If you enable 'damping' (smooth inertia), you MUST call controls.update() inside your animation loop, otherwise it won't work.
If you set controls.enableDamping = true to get smooth, buttery camera movements, what must you do in your render loop?
- āCall controls.update()
- āCall renderer.clear()
- āNothing, it's automatic
In React Three Fiber, this is hilariously easy. You just import <OrbitControls /> from @react-three/drei and drop it in your Canvas.
Try it out! Click and drag on the 3D preview below to rotate the camera around the torus knot. Scroll to zoom in and out.
Notice that we passed autoRotate to the OrbitControls. This automatically spins the camera around the object slowly, which looks incredibly premium for product showcases.
Which prop on the R3F <OrbitControls> component makes the camera slowly spin around the target automatically without user input?
- āspin
- āautoRotate
- āanimate
You can also restrict the controls. For example, stopping the user from zooming too far in or panning the camera under the floor.
Awesome! You've given the user agency in your 3D world. They can now look at your masterpieces from any angle.
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Provide Keyboard-Accessible Alternatives to Mouse-Driven Orbiting
OrbitControls responds only to mouse drag, scroll, and touch gestures by default ā a keyboard-only user has no way to orbit the camera at all. Add explicit keyboard shortcuts or preset-view buttons alongside OrbitControls so the same navigation isn't locked behind a mouse.
<button onClick={() => setCameraView('front')}>Front View</button>SEO Implications
- 1
Interactive Camera State Has No SEO Weight, but the Concept Does
Whether a user has dragged the camera to a specific orbit angle is ephemeral runtime state with no crawlable footprint ā this page's SEO value comes from explaining OrbitControls' API and damping mechanics in text, not from any specific camera position.
Best Practices
Always Call controls.update() Every Frame When Damping Is Enabled
Enabling enableDamping without calling controls.update() inside the render loop results in controls that appear completely frozen, since the eased movement is only calculated inside that update() call.
Constrain minDistance/maxDistance and maxPolarAngle for Product-Style Scenes
Unrestricted OrbitControls let users zoom through geometry or view a scene from unintended angles (like below the floor). Set sensible bounds so the camera interaction stays polished rather than breakable.
Frequent Bugs
Setting controls.enableDamping = true in vanilla Three.js but never calling controls.update() inside the animation loop, making the camera appear unresponsive to drag input.
Damping requires per-frame recalculation to apply the eased movement ā always call controls.update() inside requestAnimationFrame when damping is enabled. In React Three Fiber, drei's OrbitControls component handles this automatically.
Real-World Examples
Auto-Rotating Product Showcase
E-commerce 3D product viewers commonly combine OrbitControls with autoRotate and constrained minDistance/maxDistance/maxPolarAngle, so the product spins invitingly when idle but still allows customers to manually inspect it from any reasonable angle without losing the object or clipping through it.
<OrbitControls
autoRotate
autoRotateSpeed={1.5}
minDistance={2}
maxDistance={8}
maxPolarAngle={Math.PI / 2}
/>