šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
HTML MASTER CLASS /// LEARN TAGS /// BUILD STRUCTURE /// SEMANTIC WEB /// HTML MASTER CLASS /// LEARN TAGS ///

Camera Controls in Three.js 3D WebGL

Learn about Camera Controls in this comprehensive Three.js 3D WebGL tutorial. Learn how to use OrbitControls to allow users to pan, zoom, and rotate around your scenes.

⚔ Total XP: 0|šŸ’» threejs XP: 0

Skill Matrix

UNLOCK NODES BY LEARNING NEW TAGS.

System Hub

Core camera control concepts.

Quick Quiz //

What happens if you enable damping but never call controls.update()?


šŸš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
šŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.

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 camera
localhost:3000
Browser Preview
WebGL Output
3D 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';
localhost:3000
Browser Preview
WebGL Output
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!
localhost:3000
Browser Preview
WebGL Output
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);
}
localhost:3000
Browser Preview
WebGL Output
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() {
  ???
}
localhost:3000
Browser Preview
WebGL Output
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>
localhost:3000
Browser Preview
WebGL Output
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 />);
localhost:3000
Browser Preview
WebGL Output
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} />
localhost:3000
Browser Preview
WebGL Output
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
/>
localhost:3000
Browser Preview
WebGL Output
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!
localhost:3000
Browser Preview
WebGL Output
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

ChromeSupported

Fully supported.

FirefoxSupported

Fully supported.

SafariSupported

Fully supported.

EdgeSupported

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

THE BUG

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.

THE FIX

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}
/>

Interview Prep

?Frequently Asked Questions

Pascual Vila

Pascual Vila

Frontend Instructor // Code Syllabus

Common Pitfalls & Errors

The Error //

Not reading error messages carefully

Uncaught TypeError: Cannot read properties of undefined (reading 'length') // Solution: Ensure the variable you are calling .length on is initialized as a string or an array, not undefined.

The Solution //

Most of the time, the compiler or interpreter tells you exactly what line caused the crash and why. Read stack traces from the top down to identify the root cause.

The Error //

Hardcoding sensitive credentials

// Wrong const API_KEY = 'sk-123456789'; // Correct const API_KEY = process.env.API_KEY;

The Solution //

Never hardcode API keys, passwords, or secrets in your source code. Use environment variables (.env files) to keep them secure and out of version control.

Continue Learning