← All writing

Technical note · 2026-09-14

Canvas hit testing: keeping drawing and pointer coordinates aligned

Separate viewport, logical canvas, model and bitmap coordinates to explain why a visible shape can become difficult to select after scrolling or scaling.

Exploration · Principles and possible approaches inspired by a project context.

  • Canvas
  • Coordinate transforms
  • Hit testing
  • Cross-platform UI

Related project: Custom furniture design and quotation system. Its drawing and selection logic motivate a reusable coordinate model for diagrams, seating maps and layout editors.

One point, several coordinate systems

Pointer events, the displayed canvas rectangle, logical drawing dimensions and business geometry need not share units. Device pixel ratio adds a bitmap resolution that may exceed the CSS display size.

Confusing these spaces can make selection work at one size but drift after scrolling, scaling or changing devices.

Map viewport positions into logical coordinates

For an axis-aligned canvas with no border, padding, rotation or skew:

localX = (clientX - rect.left) × logicalWidth / rect.width
localY = (clientY - rect.top)  × logicalHeight / rect.height

Both operands must use viewport coordinates. Web getBoundingClientRect() includes padding and borders, so styled canvases require the actual content rectangle. See MDN’s coordinate description.

The project measures the canvas at interaction time. This avoids relying indefinitely on its pre-scroll position. Other hosts still require checking what their event coordinates mean.

Invert the same transform used for drawing

A centered preview scales model geometry and adds an offset. Hit testing should reverse that exact operation:

drawing: screen = model × scale + offset
testing: model  = (screen - offset) / scale

Share preview dimensions and scale. Rotation or nested transforms require composition and an inverse matrix, not accumulating correction constants.

DPR controls resolution

A larger bitmap combined with a scaled drawing context can preserve logical dimensions while increasing sharpness. Do not multiply pointer positions by DPR again when testing in logical space. See devicePixelRatio.

Changing the canvas width or height resets context state. Reestablish the drawing transform after resizing.

Define overlapping hit regions

Shared edges can match multiple objects. Choose an explicit priority based on visual order, editability or the application’s geometry rules. Decide separately whether labels, rulers and whitespace are interactive.

Test corners, shared boundaries and blank areas across scroll positions, sizes and pixel ratios. A model point transformed forward and then backward should return to its original position within numerical tolerance.