Files
upstream-tldraw/apps/docs/content/sdk-features/camera.mdx
upstream-mirror a774ac673a
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
2026-09-23 16:12:52 +05:00

227 lines
10 KiB
Plaintext

---
title: Camera system
created_at: 12/20/2025
updated_at: 12/29/2025
keywords:
- camera
- zoom
- pan
- viewport
- constraints
- animation
status: published
date: 12/20/2025
readability: 9
voice: 8
completeness: 10
accuracy: 9
notes: Could use more direct "you" address and questions-as-transitions per voice guide. Some passive constructions remain.
---
The camera controls which part of the infinite canvas is visible. It holds the viewport's position and zoom, converts between screen space and page space, and responds to user input like wheel, trackpad, keyboard, and touch. You can constrain it to a bounded area, move it programmatically with animation, and follow other users' viewports in collaborative sessions.
## How it works
The camera represents the viewport's position and zoom using three values: `x` and `y` for position in page space, and `z` for zoom level. A zoom of `1` means 100%, `0.5` is 50%, and `2` is 200%. The camera's `x` and `y` are the page-space offset of the viewport: the page point at the viewport's top-left corner is `(-x, -y)`.
The camera converts between screen space (browser pixels) and page space (the canvas). Use [Editor#screenToPage](?) to turn a mouse position into a canvas point and [Editor#pageToScreen](?) to go the other way. See [Coordinates](/sdk-features/coordinates) for the full set of conversions.
```typescript
const pagePoint = editor.screenToPage({ x: event.clientX, y: event.clientY })
const screenPoint = editor.pageToScreen({ x: shape.x, y: shape.y })
```
Read the camera with [Editor#getCamera](?) or [Editor#getZoomLevel](?), and read the visible page area with [Editor#getViewportPageBounds](?). All of these are reactive, so you can use them in `track` components or `useValue`.
## Camera options
Configure the camera with [TLCameraOptions](?). Pass the initial options through the `options.camera` prop, or change them at runtime with [Editor#setCameraOptions](?), which re-applies the constraints to the current camera immediately:
```tsx
<Tldraw options={{ camera: { wheelBehavior: 'zoom' } }} />
```
```typescript
editor.setCameraOptions({
isLocked: false,
wheelBehavior: 'pan',
panSpeed: 1,
zoomSpeed: 1,
zoomSteps: [0.1, 0.25, 0.5, 1, 2, 4, 8],
})
```
Set `isLocked` to freeze the camera for fixed-viewport apps. Camera methods then do nothing unless you pass `force: true`.
`wheelBehavior` decides what the mouse wheel or trackpad scroll does: `'pan'`, `'zoom'`, or `'none'`. If the user has set an input mode in their preferences, that preference wins between the two: `'trackpad'` pans and `'mouse'` zooms. `'none'` disables the wheel regardless of the preference.
`panSpeed` and `zoomSpeed` are multipliers on input sensitivity. Values below 1 slow movement down, values above 1 speed it up.
`zoomSteps` lists the discrete zoom levels used by zoom in and zoom out. The first value is the minimum zoom and the last is the maximum; the camera clamps to this range even without constraints.
## Camera constraints
Camera constraints limit where users can navigate. Use them for presentations, guided experiences, or applications with fixed content areas:
```typescript
editor.setCameraOptions({
constraints: {
bounds: { x: 0, y: 0, w: 1920, h: 1080 },
padding: { x: 50, y: 50 },
origin: { x: 0.5, y: 0.5 },
initialZoom: 'fit-min',
baseZoom: 'default',
behavior: 'inside',
},
})
```
The `bounds` define the constrained area in page space. The camera restricts panning outside this rectangle based on the `behavior` setting.
The `padding` adds a screen-space margin inside the viewport so content doesn't touch the edges.
The `origin` positions the bounds within the viewport when an axis uses `'fixed'` behavior, when `'contain'` is zoomed out below the fit zoom, and when the camera resets. `{ x: 0.5, y: 0.5 }` centers the bounds; `{ x: 0, y: 0 }` aligns them top-left.
### Zoom fitting
`initialZoom` is the zoom the camera starts at and returns to on reset. `baseZoom` is the zoom that `zoomSteps` are multiplied by, so a step of `1` means "the base zoom" rather than 100%. Both accept the same values:
| Value | Description |
| --------------- | ----------------------------------------------------------------------- |
| `'default'` | 100% zoom |
| `'fit-x'` | The bounds' width fills the viewport width |
| `'fit-y'` | The bounds' height fills the viewport height |
| `'fit-min'` | The smaller axis fills the viewport; the larger axis may extend past it |
| `'fit-max'` | The larger axis fills the viewport, so the full bounds stay visible |
| `'fit-x-100'` | `fit-x` or 100%, whichever is smaller |
| `'fit-y-100'` | `fit-y` or 100%, whichever is smaller |
| `'fit-min-100'` | `fit-min` or 100%, whichever is smaller |
| `'fit-max-100'` | `fit-max` or 100%, whichever is smaller |
### Constraint behaviors
The `behavior` option controls how the bounds constrain camera movement:
| Value | Description |
| ----------- | ------------------------------------------------------------------------------- |
| `'free'` | The bounds are ignored |
| `'fixed'` | The bounds are pinned at the origin; the user can't pan |
| `'inside'` | The bounds stay completely within the viewport |
| `'outside'` | The bounds stay touching the viewport |
| `'contain'` | `'fixed'` when zoomed out below the fit zoom, `'inside'` when zoomed in past it |
Set behavior per axis for asymmetric constraints:
```typescript
behavior: {
x: 'free', // Horizontal panning unrestricted
y: 'inside', // Vertical panning keeps bounds visible
}
```
## Camera methods
The camera-move methods below ([Editor#setCamera](?), [Editor#centerOnPoint](?), [Editor#zoomIn](?), [Editor#zoomOut](?), [Editor#zoomToFit](?), [Editor#zoomToSelection](?), [Editor#zoomToBounds](?), and [Editor#resetZoom](?)) accept optional [TLCameraMoveOptions](?):
- `animation` - animate the move with `duration` and `easing`
- `immediate` - move the camera immediately rather than on the next tick
- `force` - move the camera even when `isLocked` is true
- `reset` - reset the camera to the constraints' initial zoom and origin
### Basic navigation
Move the camera to a specific position and zoom:
```typescript
editor.setCamera({ x: -500, y: -300, z: 1.5 })
```
Center the viewport on a point:
```typescript
editor.centerOnPoint({ x: 1000, y: 500 })
```
Zoom in or out. Both methods accept an optional screen point to zoom toward:
```typescript
editor.zoomIn()
editor.zoomOut()
editor.zoomIn(editor.inputs.getCurrentScreenPoint(), { animation: { duration: 200 } })
```
### Zoom to content
Focus the camera on shapes or bounds:
```typescript
// Fit all shapes on the current page
editor.zoomToFit()
// Fit the current selection
editor.zoomToSelection()
// Reset zoom to 100%. With constraints, toggles between the initial zoom and 100%
editor.resetZoom()
// Fit specific bounds with padding
const bounds = { x: 0, y: 0, w: 1000, h: 800 }
editor.zoomToBounds(bounds, { inset: 100 })
```
`zoomToBounds` accepts `inset` to add screen-space padding around the bounds and `targetZoom` to cap the zoom level.
### Animated movement
Add smooth transitions with the `animation` option. The [EASINGS](?) object provides common easing functions:
```typescript
import { EASINGS } from 'tldraw'
editor.setCamera(
{ x: 0, y: 0, z: 1 },
{
animation: {
duration: 500,
easing: EASINGS.easeInOutCubic,
},
}
)
```
Camera animations stop automatically when the user pans or zooms: user input takes precedence over programmatic movement. You can also stop them at any time with [Editor#stopCameraAnimation](?). See [Animation](/sdk-features/animation) for more on animation options and user preferences.
### Momentum scrolling
Use [Editor#slideCamera](?) for kinetic scrolling, for example to keep the camera moving after a gesture ends:
```typescript
editor.slideCamera({
speed: 1,
direction: { x: 1, y: 0 },
friction: 0.1,
speedThreshold: 0.01,
})
```
`speed` is clamped to a maximum of `1` and `direction` sets the initial velocity. A `z` component on `direction` slides the zoom as well. `friction` controls how fast the camera decelerates (higher stops sooner) and defaults to `editor.options.cameraSlideFriction`. The slide ends once the speed drops below `speedThreshold`.
### Quick zoom navigation
The default tldraw UI has a quick zoom mode. Press `z` to select the zoom tool, then hold Shift. The camera zooms out to show your current viewport plus everything on the page, and a brush marks where you'll land. Move the cursor to place the brush and release Shift to zoom there. Press Escape to cancel and return to the original view.
## Collaboration features
Call [Editor#startFollowingUser](?) to track another user's viewport, or [Editor#zoomToUser](?) to jump to their cursor once. While following, the camera fits the other user's viewport inside yours: if the aspect ratios differ, the zoom adjusts so their whole viewport stays visible.
See [User following](/sdk-features/user-following) for details.
## Related examples
- [Camera options](/examples/configuration/camera-options) - Configure the camera's options and constraints, including zoom behavior, pan speed, and camera bounds.
- [Image annotator](/examples/use-cases/image-annotator) - Configure camera options for a fixed-viewport annotation app.
- [Slideshow (fixed camera)](/examples/use-cases/slideshow) - A slideshow with a fixed camera using camera constraints.
- [Lock camera zoom](/examples/editor-api/lock-camera-zoom) - Lock the camera at a specific zoom level.
- [Zoom to bounds](/examples/editor-api/zoom-to-bounds) - Zoom the camera to specific bounds with `zoomToBounds`.
- [Scrollable container](/examples/layout/scroll) - Use the editor inside a scrollable container with mousewheel handling.