Back to Tldraw

Ticks

apps/docs/content/sdk-features/ticks.mdx

5.4.04.7 KB
Original Source

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 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.

  • Events - Overview of all editor events including tick
  • Edge scrolling - Detailed documentation on edge scrolling behavior
  • Snowstorm example - Uses tick events to animate falling snowflakes