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

128 lines
5.0 KiB
Plaintext

---
title: Ticks
created_at: 12/20/2025
updated_at: 12/20/2025
keywords:
- tick
- frame
- animation
- TickManager
- requestAnimationFrame
status: published
date: 12/20/2025
readability: 9
voice: 8
completeness: 9
accuracy: 10
notes: ''
---
The tick system provides a frame-synchronized update loop for the editor. On every animation frame the editor emits a `frame` event and then a `tick` event, each with the elapsed time in milliseconds since the last frame.
While pointer and keyboard events fire in response to user input, tick events fire continuously. Use them when you need updates that run every frame regardless of user interaction.
> The editor emits `frame` first so its own bookkeeping (pointer velocity, following a collaborator) runs before `tick` handlers. Use `tick` in application code.
## Subscribing to tick events
The most common way to use ticks is by subscribing to the `tick` event on the editor. The callback receives the elapsed time in milliseconds since the last frame:
```tsx
import { Tldraw } from 'tldraw'
import 'tldraw/tldraw.css'
export default function TickExample() {
return (
<div style={{ position: 'fixed', inset: 0 }}>
<Tldraw
onMount={(editor) => {
const handleTick = (elapsed: number) => {
// elapsed is typically ~16ms at 60fps
updateAnimation(elapsed)
}
editor.on('tick', handleTick)
// Clean up when done
return () => editor.off('tick', handleTick)
}}
/>
</div>
)
}
```
Remember to unsubscribe when your component unmounts or when you no longer need tick updates. Scale movement by `elapsed` rather than assuming a fixed framerate. For one-off work on the next frame, use `editor.timers.requestAnimationFrame`, which is cleaned up when the editor is disposed.
## Tick events in tools
When building custom tools using the state machine pattern, you can handle tick events by implementing the `onTick` method on your [StateNode](?). The editor dispatches tick events through the state tree after flushing any pending pointer events for that frame, so your active tool states receive them automatically:
```typescript
import { StateNode, TLTickEventInfo } from 'tldraw'
export class MyDraggingState extends StateNode {
static override id = 'dragging'
override onTick({ elapsed }: TLTickEventInfo) {
// Update something every frame while this state is active
this.updateDragPosition(elapsed)
}
private updateDragPosition(elapsed: number) {
// Your frame-based logic here
}
}
```
[TLTickEventInfo](?) contains the elapsed time in milliseconds.
## Edge scrolling example
The most common use of `onTick` in tools is edge scrolling during drag operations. When you drag near the edge of the viewport, the canvas scrolls automatically. Here's how tldraw's built-in `Translating` state handles it:
```typescript
import { StateNode, TLTickEventInfo } from 'tldraw'
export class Translating extends StateNode {
static override id = 'translating'
override onTick({ elapsed }: TLTickEventInfo) {
const { editor } = this
if (!editor.inputs.getIsDragging() || editor.inputs.getIsPanning()) return
editor.edgeScrollManager.updateEdgeScrolling(elapsed)
}
}
```
The [EdgeScrollManager](?) accumulates elapsed time to create a smooth acceleration effect. After a short delay, scrolling begins slowly and speeds up the longer you hold near the edge.
For more details on edge scrolling, see the [edge scrolling](/sdk-features/edge-scrolling) documentation.
## How the editor uses ticks internally
The editor uses tick and frame events for several internal features.
The [ScribbleManager](?) animates the trails you see while erasing, using the laser pointer, or scribble-selecting. On each tick it adds new points to active scribbles and shrinks them from the tail, so the trail fades.
The [InputsManager](?) computes pointer velocity on each `frame` event. Read it with `editor.inputs.getPointerVelocity()`; the select tool uses it to decide when to snap or drop into a container.
Camera methods like `editor.zoomIn()` and `editor.zoomToFit()` animate when you pass an `animation` option. The animation subscribes to `tick` for its duration and unsubscribes when complete.
## When to use tick events
Tick events are appropriate when you need continuous updates that run every frame:
- Animations and interpolation that should run regardless of user input
- Edge scrolling during drag operations
- Physics simulations or particle systems
- Debouncing based on frame counts rather than timeouts
Don't use tick events for responding to user input. Pointer, keyboard, and wheel events are better for that since they fire immediately when the user acts. Tick events add a frame of latency.
## Related
- [Events](/sdk-features/events) - Overview of all editor events including tick
- [Edge scrolling](/sdk-features/edge-scrolling) - Detailed documentation on edge scrolling behavior
- [Snowstorm example](https://github.com/tldraw/tldraw/tree/main/apps/examples/src/examples/use-cases/snowstorm) - Uses tick events to animate falling snowflakes