Navigate
Search topics across all sections
Camera Controls
CameraControls is the upgraded version of OrbitControls. It gives you the same orbit, zoom, and pan, but adds smooth animated transitions, auto-framing, and full programmatic control. If your app needs to fly the camera from point A to point B, this is the component you want.
You build a product viewer with OrbitControls. The client asks you to add a 'click on a part to zoom in' feature. You try setting the camera position directly, but it snaps instantly. There is no way to animate the transition. You end up rewriting half your camera logic to switch to CameraControls.
Camera jumps to new position with no animation. OrbitControls.target.set() provides no transition support.
Real-world
Think of CameraControls as a drone camera. OrbitControls is like spinning a globe on your desk — simple and direct. But a drone can do much more. You give it a flight plan: "Fly from here to there, point at that building, and zoom in smoothly."
That flight plan is setLookAt. You tell the drone where to go (camera position) and what to look at (target position), and it glides there gracefully. You can even chain multiple flight plans for a guided tour.
The setLookAt Flight Plan
Every animated camera move follows the same pattern.
Get ref
useRef to access controls
Call setLookAt
(posX, posY, posZ, targetX, targetY, targetZ, animate)
Camera flies
Smooth transition over smoothTime seconds
Promise resolves
Chain the next move with await
Guided Walkthrough
Let us build a camera system that can fly between views.
Step 1 — Set up CameraControls with a ref
import { CameraControls } from '@react-three/drei'
import { useRef } from 'react'
function Experience() {
const controls = useRef(null)
return (
<CameraControls
ref={controls}
smoothTime={0.5}
makeDefault
/>
)
}The ref gives you access to all the camera methods. smoothTime controls how long transitions take. makeDefault keeps everything in sync just like with OrbitControls.
Step 2 — Fly to a position with setLookAt
const goToFront = () =>
controls.current?.setLookAt(
0, 0, 5, // camera position
0, 0, 0, // look-at target
true // animate the transition
)
const goToTop = () =>
controls.current?.setLookAt(
0, 5, 0.01, // slightly off-axis to avoid gimbal lock
0, 0, 0,
true
)setLookAt takes six numbers: where to put the camera, and where to point it. The last argument enables smooth animation. Without it, the camera teleports.
Step 3 — Chain transitions for a guided tour
async function guidedTour(controls) {
await controls.setLookAt(10, 8, 10, 0, 0, 0, true)
await new Promise(r => setTimeout(r, 2000))
await controls.setLookAt(2, 1, 2, 1, 0, 0, true)
await controls.setLookAt(5, 5, 5, 0, 0, 0, true)
}Every transition method returns a Promise. Use await to wait for one move to finish before starting the next. Add pauses with setTimeout between stops for a cinematic feel.
What you just learned
CameraControls gives you orbit + zoom + pan, plus smooth animated transitions
setLookAt is the core method: tell it where to go and what to look at
smoothTime controls how long transitions take (0.3 to 1.0 seconds feels natural)
All methods return Promises so you can chain transitions with await
Use fitToBox to automatically frame an object into view
Question
CameraControls can do everything OrbitControls does, plus animated transitions. So why would you ever pick OrbitControls? Think about bundle size, simplicity, and the principle of using the smallest tool that gets the job done.
Think about it...
You call setLookAt(0, 0, 5, 0, 0, 0, true) and then immediately call setLookAt(10, 10, 10, 0, 0, 0, true). What happens?
Try These Challenges
Put what you learned into practice. Try each challenge in the demo above using the Leva controls, then check the solution.
Try This!
Beginner
Set smoothTime to 2 -- slow-motion camera flight!
Try This!
Intermediate
Click all 3 objects quickly -- does the camera queue up?
Try This!
Beginner
Set transitionSpeed to 5 -- snappy transitions!
These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
const controls = useRef<OrbitControlsImpl>(null)
const focusOnPart = (pos) => {
// OrbitControls has no animation system!
// This snaps instantly — jarring for users
controls.current.target.set(...pos)
controls.current.update()
}const controls = useRef<CameraControlsImpl>(null)
// Uses default smoothTime — might feel snappy
controls.current?.setLookAt(5, 5, 5, 0, 0, 0, true)function Experience() {
return (
<>
{/* Both attach listeners and move the camera! */}
<OrbitControls makeDefault />
<CameraControls makeDefault />
</>
)
}Best Practices
Set smoothTime
Configure smoothTime in seconds. Values between 0.3 and 1.0 feel natural. Change it dynamically before specific transitions if needed.
Use fitToBox
Call fitToBox(mesh, true, padding) to automatically frame any object into view. Perfect for product viewers where models vary in size.
Chain with await
All transition methods return Promises. Use await to sequence camera moves for guided tours and cinematic fly-throughs.
Add collision boundaries
Use distance limits, polar/azimuth constraints, and colliderMeshes to prevent the camera from going through walls or flying off into space.