Skip to content

Story Canvas – Technical Documentation

This document provides a concise description of the algorithms and architectural decisions used to create, render, and manipulate the story canvas. File names and function identifiers are referenced for orientation; implementation details such as literal values or code excerpts are intentionally omitted.


1. Canvas Creation

The canvas is instantiated by the StoryCanvasBG component. At mount time this component:

  1. Retrieves the canonical width and height from the context (CANVAS_WIDTH, CANVAS_HEIGHT).
  2. Produces a scalable background pattern that is repeated across the entire surface.
  3. Hosts an optional overlay for coordinate markers, which can be toggled for debugging purposes.

No visual transformations are applied at this stage; the component simply provides the drawable surface and background styling.


2. Canvas Rendering

The StoryCanvas component is responsible for composing the visible scene. It performs the following steps during every render cycle:

  1. Reads the current viewport from StoryCanvasContext. The viewport contains the top-left offset (x, y) and the zoom factor (zoom).
  2. Calculates the transform origin using the geometric centre of the canvas.
  3. Applies a single CSS transform to a wrapper <div>:
  4. translate by the negative viewport offsets so that the desired canvas region aligns with the window origin.
  5. scale by the viewport zoom factor to realise magnification.
  6. Embeds StoryCanvasBG and the collection of child nodes as its descendants.

Because all visual changes are encapsulated in the composite transform, the browser's compositor can optimise the operation, resulting in smooth interaction even on large scenes.


3. Viewport Management

StoryCanvasContext keeps the viewport in reactive state and exposes a small set of pure functions:

  • updateViewport – merges a partial viewport with the current state while clamping the result so the canvas never scrolls outside the screen.
  • resetViewport – centres the canvas and restores the neutral zoom level.
  • zoomIn / zoomOut – adjust the zoom factor in fixed increments while enforcing a configurable min-max range.

These functions are the only entry points that modify the viewport. Components interact with them directly, ensuring a single source of truth.


4. Canvas Translation (Panning)

The PanGesture component attaches pointer- and wheel-based gesture handlers from the @use-gesture/react library. The algorithm is as follows:

  1. Pointer Drag
  2. On gesture start, cache the current viewport in an internal memo object.
  3. While the pointer moves, compute delta values (Δx, Δy) relative to the gesture start.
  4. Pass memo.x − Δx and memo.y − Δy to updateViewport.
  5. Wheel Scroll
  6. On wheel events, add the device-reported deltas directly to the viewport and forward the result to updateViewport.

Both pathways rely solely on viewport mutation; the canvas itself remains stateless.


5. Zoom Management

Zooming is implemented entirely inside StoryCanvasContext via zoomIn and zoomOut. Each call recalculates the zoom factor and updates the viewport. Subsequent renders of StoryCanvas apply the new scale as part of the transform pipeline described in Section 2.


6. Child Node Positioning

Individual elements rendered on the canvas are wrapped by StoryCanvasChild, which delegates drag interaction to DragGesture. The procedure is:

  1. Fetch the persisted position from getChildPosition.
  2. During a drag gesture, calculate the movement delta and divide by the current viewport.zoom so that perceived distance on screen is consistent at any magnification.
  3. Optionally round the coordinates to a grid step to facilitate alignment.
  4. Commit the final position through updateChildPosition, which causes persistence in local storage.

Because each child maintains its own transform relative to the canvas, node motion composes naturally with global panning and zooming.


7. Data Persistence

Both viewport and child positions are serialised to local storage through reactive effects inside StoryCanvasContext. On application start the stored state is hydrated, enabling a seamless continuation of the previous session without additional network latency.


8. Coordinate Conventions

All positions are treated as absolute canvas coordinates. Two helper functions, toCenterRelative and toAbsolute, provide conversion to and from a centre-relative system when such representation aids calculation. These helpers are confined to the context layer, keeping the rest of the codebase agnostic of coordinate transformations.


9. Summary of Responsibilities

Concern Primary Module / Function
Surface definition StoryCanvasBG
Scene composition and transform StoryCanvas
State and algorithms StoryCanvasContext
Panning gestures PanGesture
Zoom control zoomIn, zoomOut
Node drag DragGesture / StoryCanvasChild
Persistence Local-storage effects within context

10. Misc

  1. The pan gesture can conflict with the browser's native back/forward swipe. To prevent unintended navigation the following rules are placed in globals.css:
html,
body {
  overscroll-behavior-x: none;
  overscroll-behavior-y: none;
}

Future work: apply these rules conditionally so the restriction is limited to the canvas view.