Files
upstream-tldraw/apps/docs/content/sdk-features/coordinates.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

151 lines
6.5 KiB
Plaintext

---
title: Coordinates
created_at: 12/20/2025
updated_at: 12/29/2025
keywords:
- coordinates
- screen
- page
- viewport
- transform
reviewed_by: steveruizok
status: published
date: 12/20/2025
readability: 9
voice: 8
completeness: 9
accuracy: 9
notes: Could use more questions-as-transitions per voice guide. Consider adding visual diagram and API reference links.
---
The editor uses three coordinate systems: screen space, viewport space, and page space. When you move the mouse over the canvas, the browser gives you screen coordinates. To create a shape at that location, you need to convert those coordinates to page space.
The editor provides methods to convert between these coordinate systems. You'll use these when building custom tools, positioning DOM overlays, or responding to pointer events. The [Camera](/sdk-features/camera) article explains the camera values these conversions depend on.
| Method | Description |
| -------------------------- | ------------------------------------------------------------------- |
| [Editor#screenToPage](?) | Convert a screen point to page space. |
| [Editor#pageToScreen](?) | Convert a page point to screen space. |
| [Editor#pageToViewport](?) | Convert a page point to viewport space (relative to the container). |
## The three coordinate systems
### Screen space
Screen space uses pixel coordinates from the browser window's top-left corner. These are the values you get from `MouseEvent.clientX` and `MouseEvent.clientY`. Screen coordinates include any space outside the editor container, like browser chrome or other page content.
### Viewport space
Viewport space uses pixel coordinates from the editor container's top-left corner. This accounts for where the editor sits on the page. If the editor is embedded in a scrollable element or positioned away from the browser's origin, viewport coordinates differ from screen coordinates by that offset.
The editor tracks the container's position in [Editor#getViewportScreenBounds](?).
### Page space
Page space is the infinite canvas itself. A shape at `x: 100, y: 200` stays at those coordinates regardless of how the user pans or zooms. Top-level shapes store their position in page space; children of frames and groups store it relative to their parent. See [Parenting](/sdk-features/parenting) for the helpers that convert between shape space and page space.
The camera determines which part of page space is visible. When you zoom in, the same page-space region takes up more screen pixels. When you pan, different page-space coordinates come into view.
## Coordinate transformations
### Screen to page space
Use [Editor#screenToPage](?) to convert screen coordinates to page coordinates. This is the most common transformation. It accounts for the editor container's position, the camera position, and the zoom level:
```typescript
// Convert mouse event coordinates to page space
const pagePoint = editor.screenToPage({ x: event.clientX, y: event.clientY })
// Create a shape at the clicked location
editor.createShape({
type: 'geo',
x: pagePoint.x,
y: pagePoint.y,
props: { w: 100, h: 100, geo: 'rectangle' },
})
```
### Page to screen space
Use [Editor#pageToScreen](?) to convert page coordinates to screen coordinates, for example to position a DOM element that lives outside the editor container:
```typescript
// Convert shape position to screen coordinates
const shape = editor.getShape(shapeId)
if (!shape) return
const screenPoint = editor.pageToScreen({ x: shape.x, y: shape.y })
// Position a DOM element at the shape's screen location
element.style.left = `${screenPoint.x}px`
element.style.top = `${screenPoint.y}px`
```
### Page to viewport space
Use [Editor#pageToViewport](?) to convert page coordinates to viewport coordinates. This is `pageToScreen()` without the container offset, so use it for canvas rendering or for positioning elements inside the editor container:
```typescript
// Get viewport coordinates for a page point
const viewportPoint = editor.pageToViewport({ x: 500, y: 300 })
// Check if a point is visible in the viewport
const viewportBounds = editor.getViewportScreenBounds()
const isVisible =
viewportPoint.x >= 0 &&
viewportPoint.x <= viewportBounds.w &&
viewportPoint.y >= 0 &&
viewportPoint.y <= viewportBounds.h
```
## Viewport bounds
Use [Editor#getViewportScreenBounds](?) to get the editor container's position and size in screen space:
```typescript
const screenBounds = editor.getViewportScreenBounds()
// screenBounds.x, screenBounds.y - container position in screen space
// screenBounds.w, screenBounds.h - container dimensions in pixels
```
Use [Editor#getViewportPageBounds](?) to get the visible area in page space:
```typescript
const pageBounds = editor.getViewportPageBounds()
// pageBounds.x, pageBounds.y - top-left corner of visible area in page space
// pageBounds.w, pageBounds.h - visible area dimensions in page units
```
Both are reactive, so you can read them inside `track` components or `useValue` and re-render when the user pans or zooms. At higher zoom levels, the visible page-space area is smaller.
## Using the inputs manager
The editor's inputs manager tracks the current pointer position, so you can read it from anywhere in your code:
```typescript
// Get the current pointer position
const screenPoint = editor.inputs.getCurrentScreenPoint()
const pagePoint = editor.inputs.getCurrentPagePoint()
// Get the position where the current drag started
const originScreenPoint = editor.inputs.getOriginScreenPoint()
const originPagePoint = editor.inputs.getOriginPagePoint()
```
Despite the name, the inputs manager's "screen point" is relative to the editor container, which this article calls viewport space. Don't pass it to `screenToPage()` and don't subtract `getViewportScreenBounds()` from it.
Pointer event info is different: its `point` property holds `clientX`/`clientY`, so it is in screen space and converts with `screenToPage()`:
```typescript
editor.on('event', (event) => {
if (event.type === 'pointer' && event.name === 'pointer_down') {
const pagePoint = editor.screenToPage(event.point)
// Use pagePoint for shape manipulation
}
})
```
## Related examples
- [Reactive inputs](/examples/editor-api/reactive-inputs) - Display page and screen coordinates reactively as the pointer moves.
- [Selection UI](/examples/ui/selection-ui) - Use selection bounds in screen space to position custom UI around shapes.