Edge scrolling
Configure how the camera auto-scrolls when dragging near the viewport edge.
import { useState } from 'react'
import { Editor, TLGeoShape, Tldraw, toRichText } from 'tldraw'
import 'tldraw/tldraw.css'
import './edge-scrolling.css'
// There's a guide at the bottom of this file!
// [1]
interface EdgeScrollValues {
edgeScrollSpeed: number
edgeScrollDelay: number
edgeScrollEaseDuration: number
edgeScrollDistance: number
}
const DEFAULTS: EdgeScrollValues = {
edgeScrollSpeed: 25,
edgeScrollDelay: 200,
edgeScrollEaseDuration: 200,
edgeScrollDistance: 8,
}
const CONTROLS = [
{ key: 'edgeScrollSpeed', label: 'Speed (px per tick)', min: 0, max: 100, step: 5 },
{ key: 'edgeScrollDelay', label: 'Delay (ms)', min: 0, max: 1000, step: 50 },
{ key: 'edgeScrollEaseDuration', label: 'Ease duration (ms)', min: 0, max: 1000, step: 50 },
{ key: 'edgeScrollDistance', label: 'Edge zone size (px)', min: 0, max: 240, step: 8 },
] as const
// [2]
function ControlsPanel({
values,
onCommit,
}: {
values: EdgeScrollValues
onCommit(next: EdgeScrollValues): void
}) {
const [pending, setPending] = useState(values)
return (
<div className="edge-scrolling__panel">
<div className="edge-scrolling__title">Edge scrolling options</div>
{CONTROLS.map((control) => {
// read the slider's value from the DOM at commit time so the commit
// can't lag behind the last change event
const commit = (input: HTMLInputElement) =>
onCommit({ ...pending, [control.key]: Number(input.value) })
return (
<label key={control.key} className="edge-scrolling__row">
<span>
{control.label}: <b>{pending[control.key]}</b>
</span>
<input
type="range"
min={control.min}
max={control.max}
step={control.step}
value={pending[control.key]}
onChange={(e) =>
setPending({ ...pending, [control.key]: Number(e.currentTarget.value) })
}
onPointerUp={(e) => commit(e.currentTarget)}
onKeyUp={(e) => commit(e.currentTarget)}
onBlur={(e) => commit(e.currentTarget)}
/>
</label>
)
})}
<button
className="edge-scrolling__reset"
onClick={() => {
setPending(DEFAULTS)
onCommit(DEFAULTS)
}}
>
Reset to defaults
</button>
</div>
)
}
// [3]
function createDemoShapes(editor: Editor) {
// changing the options prop recreates the editor but keeps the store, so
// only create the demo shapes on the first mount
if (editor.getCurrentPageShapeIds().size > 0) return
editor.createShape({
type: 'text',
x: 120,
y: 80,
props: {
richText: toRichText('Drag a shape toward the edge of the window to edge scroll'),
},
})
const colors = ['blue', 'orange', 'green'] as const
colors.forEach((color, i) => {
editor.createShape<TLGeoShape>({
type: 'geo',
x: 150 + i * 220,
y: 200,
props: { w: 140, h: 140, fill: 'solid', color },
})
})
}
export default function EdgeScrollingExample() {
const [values, setValues] = useState(DEFAULTS)
return (
<div className="tldraw__editor edge-scrolling">
{/* [4] */}
<Tldraw options={values} onMount={createDemoShapes} />
<ControlsPanel values={values} onCommit={setValues} />
</div>
)
}
/*
This example shows how to configure edge scrolling: the behavior that
automatically pans the camera when you drag a shape (or a selection brush)
close to the edge of the viewport.
[1]
Edge scrolling is configured through four editor options:
- edgeScrollSpeed: the base scroll speed in pixels per tick (default 25).
The speed also scales with how close the pointer is to the edge, and is
reduced on small screens.
- edgeScrollDelay: how long (ms) the pointer must stay near the edge before
scrolling starts (default 200). Raising it prevents accidental scrolls.
- edgeScrollEaseDuration: how long (ms) the scroll takes to accelerate from
zero to full speed once it starts (default 200).
- edgeScrollDistance: the width in pixels of the edge zone that triggers
scrolling (default 8). Pointer positions outside the window scroll at full
speed. Try a large zone (200+): since you grab a shape near its middle,
scrolling then kicks in as soon as the shape itself touches the window
edge, rather than waiting for the pointer to get there.
[2]
The sliders commit when you release them rather than while you drag.
That's because editor options are read once when the editor instance is
created: changing the options prop tears down and recreates the editor, so
we don't want to do it dozens of times per second mid-drag. Commits fire on
pointer release, key release (so keyboard adjustment works too), and blur —
re-committing an unchanged value is harmless, since the Tldraw component
shallow-compares its options.
[3]
A few shapes to drag around. Grab one and hold it near a window edge to see
the camera start scrolling; adjust the sliders and try again.
[4]
Options are passed via the Tldraw component's options prop. Any option from
TldrawOptions can be overridden this way; unspecified options keep their
defaults.
*/
Edge scrolling automatically pans the camera when you drag a shape or selection brush close to the edge of the viewport. This example provides sliders for the four options that control it, passed through the Tldraw component's options prop:
edgeScrollSpeed: base scroll speed in pixels per tick (default 25)edgeScrollDelay: how long the pointer must linger near the edge before scrolling starts (default 200ms)edgeScrollEaseDuration: how long the scroll takes to accelerate to full speed (default 200ms)edgeScrollDistance: the width of the edge zone that triggers scrolling (default 8px)
Drag one of the shapes toward a window edge to feel the current settings, then adjust the sliders and try again. The sliders commit on release because editor options are read when the editor is created: changing the options prop recreates the editor instance.
Is this page helpful?
Prev
Display optionsNext
Persistence key