Skip to content

Object snapping: moves and resizes line up with other objects and the canvas - #21

Merged
pathanin merged 4 commits into
masterfrom
object-snapping
Oct 21, 2026
Merged

pathanin merged 4 commits into
masterfrom
object-snapping

Conversation

@pathanin

Copy link
Copy Markdown
Owner

What

Moving or resizing an object (Move tool, or a shape tool on its own selected shape) now snaps it to the other objects in view and to the canvas.

  • What lines up: the left, centre and right of what you drag (top, middle and bottom on the other axis) with those of the other objects and the canvas. Each axis snaps on its own, within 8 screen px at any zoom.
  • Guides: magenta 1 px lines with a × on each point that lines up. They go on release or Esc.
  • ⌘ held (Ctrl on Windows/Linux): places it freely. Pressing or releasing it mid-drag takes effect at once.
  • Resize: only the dragged side or corner snaps; a line end snaps its point.
  • Shift keeps its meaning: a Shift-move stays on one axis and snaps the other; a Shift-resize keeps proportions (or a line's 45°) and doesn't snap.
  • ⌥-drag copies can snap to their original, so a copy dragged straight down stays in line. Arrow nudges never snap.

Follows tldraw and Excalidraw (8 px threshold, nearest match per axis, only objects in view) and Figma (on by default). The defaults (on by default, no permanent off switch, ⌘ to suspend) are recorded in task.md as not yet confirmed.

Code

  • core.js: snapLines, snapMove, snapResize, snapGuides, pure and unit-tested.
  • pointer.js: objDragUpdate() snaps the raw pointer delta on every update. The targets are worked out once per drag (frameOf() cached in scene.js, visible() from view.js).
  • view.js + style.css: #guides lines and × marks, placed in device px.
  • keyboard.js: ⌘ / Ctrl pressed or released mid-drag.
  • keyLabel(): a lone ⌘ becomes "Ctrl" off a Mac, and the options bar's hints now go through it (the shapes hint showed "⌘E" on Windows).
  • Help, README, task.md, CLAUDE.md and docs/objects.md describe it.

Testing

  • npm test: 91 pass (4 new).
  • objects.js section 35 covers: snapping to an edge, ⌘ mid-drag, the canvas centre with Shift, a resize handle, a group move, a group box resize, no snapping on a Shift-resize, an ⌥ copy, a line end, Esc, and undo/redo.
    • Chrome 226/226, WebKit 223/223.
    • 15 of the new checks fail against master, so they test snapping.
  • Other suites:
    • Chrome: smoke, preview, filledge, alpha, libs and pwa pass.
    • WebKit: smoke, preview, filledge, alpha, libs and history pass.
    • parity.js: all 23 steps equal to master.
  • history.js in Chrome on an M4: 3 pixel-exact checks fail ("undo of paste that grows", "redo of pencil after crop", "second undo of paste that grows"). They fail on master too, and CI passes them.
  • bench.js --only 5k: no regressions ("move one in 5k" +2–4%, within the ±15% noise).
  • The Ctrl path was checked only with navigator.platform faked as Win32 in macOS Chrome. This PR's Linux CI is its first real run.

Not done: equal-spacing (gap) snaps, snapping a new shape while it's drawn, a permanent off switch.

… canvas, with guides

The edges and centre of what a Move-tool drag moves or resizes snap, each axis on its own, to the
nearest edge or centre of an object in view or of the canvas, within 8 screen px (as tldraw and
Excalidraw). Magenta guides show what lines up. Command (Ctrl elsewhere) held suspends it, also
pressed or released mid-drag. Shift: a move keeps its axis lock and snaps the other one; a resize
that keeps its aspect or a line's 45 degrees doesn't snap. Arrow nudges never snap.

Core.snapLines / snapMove / snapResize / snapGuides are pure and unit-tested; objects.js section 35
checks it end to end (it fails on master).
…n centre to centre; docs and help

Guides now pass through points (a frame's corners and centre, a line's end), as tldraw and Excalidraw
draw them: an edge guide through the corners on it, a centre guide from centre to centre. A small ×
marks each point, so a guide stays readable where it lies along a black outline.

Help, README, task.md (decision), CLAUDE.md and docs/objects.md describe snapping. The options bar's
per-tool hints now go through keyLabel(), which also turns a lone ⌘ into Ctrl elsewhere (the shapes
hint said ⌘E on Windows). objects.js also checks a group box's handle snapping and that a Shift
resize doesn't.
@pathanin
pathanin merged commit 5cf0e38 into master Oct 21, 2026
3 checks passed
@pathanin
pathanin deleted the object-snapping branch October 21, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant