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
151 lines
6.5 KiB
Plaintext
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.
|