Canvas View
The player's infinite-canvas view mode — camera glides over a continuous plane, guided and free-roam input, reveal modes, performance, and accessibility.
Canvas view is the player's third view mode (alongside panel and page view):
all placed panels of a chapter share one continuous world-space plane, and a
camera glides between them along the story graph. It activates automatically
when the chapter has a canvas layout and an output
preset enables canvasView; otherwise the player falls back to panel view.
Navigation semantics don't change in canvas view. "Next" still asks the flow
engine to pick the target via graph edges, conditions, and priorities —
canvas view only changes what happens visually. panelChange, tracking
events, variables, and paywall checks behave exactly as in panel view.
The camera
The camera is a persistent position + zoom over the plane, managed by
CanvasCameraService:
- Arrivals frame the target panel (or its
enterFramingsub-rect) using the chapter'sfitMode. - Camera moves between panels follow the traversed edge's
cameraMove—direct,arc, or authoredwaypointspaths withhold,pull-back, ordivezoom profiles. Zoom interpolates logarithmically so scaling feels perceptually linear. - Motion safety: travel speed is capped (a player constant, not authorable) — durations too short for the distance are stretched.
- Interrupts: reader input during a glide cancels it — the reader owns the camera, like a map app. Navigation state is already final; only the animation stops.
- Back-navigation retraces the original edge's path with waypoints reversed.
Input model
| Input | Guided (freeRoam: "off") | Free roam |
|---|---|---|
| Tap right/left edge zone, Arrow keys | next / previous via the graph | same |
| Drag / single-finger pan | — | pan the camera |
| Pinch / Ctrl+wheel | zoom | zoom at cursor |
| Plain wheel | — | pan |
| Tap a panel | advance if it's an out-edge target of the current panel (condition-gated) | same, plus jump to already-visited panels |
Tap / Enter on a hotspot of the current panel | run the hotspot's action (as in panel view) | same |
O | toggle overview (zoom out to the whole canvas, and back) | same |
+ / - | zoom in / out | same |
With freeRoam: "between-moves" (the default) panning is available while the
camera is idle; "always" also allows grabbing the camera mid-glide.
Only the current panel's hotspots are interactive: a
press that starts on one of them is left to the hotspot instead of becoming a
pan or a panel tap, and its action and hotspot_click tracking run exactly as
in panel view. Hotspots on the other visible panels are drawn but inert.
Reveal modes
always— rendered whenever on screen.on-approach— revealed once the camera nears it, then stays revealed.on-visit— a dark veil replaces the artwork until the trail reaches the panel, so an overview zoom never spoils secret branches or gated content.
Whatever its reveal mode, a panel the paywall locks for the reader (or an
x-locked stub) renders as the
locked placeholder
from the player release after 1.2.0, and turns into its artwork in place once
the lock lifts. In 1.2.0, a revealed gated panel showed its content.
Performance
The plane is unbounded, but the working set is not:
- DOM virtualization — only panels intersecting the camera rect (inflated by one viewport in every direction) are mounted, nearest first, capped at 12 panels regardless of chapter size. The current panel is always mounted.
- Variant-by-zoom — each mounted panel selects the smallest image variant that covers its actual display width (placement width × zoom × devicePixelRatio, quantized to 256 px steps). Overview zoom mounts thumbnails; close-ups load the full-resolution encode. Re-resolution happens when the camera settles — never per frame — and is upgrade-only while a panel stays mounted, so loaded art never flickers back to a thumbnail.
- Neighbor preloading — on camera settle the player warms the current
panel's out-edge targets (high priority) and the nearest placements
(medium), each at the variant it would render at, honoring
settings.preload(strategy,panelsAhead,maxConcurrent) plus network-awareness (connection type, data-saver). - Compositing — the whole plane moves with a single transform; non-current panels use CSS containment.
Accessibility & reduced motion
- Under reduced motion — the OS
prefers-reduced-motionsetting, the reader's Settings switch, or the shell'sreducedMotioninput — the camera never glides: reframes are instant, softened by a short cross-fade; an authoredreducedMotionFallbacktransition takes precedence. - Keyboard-only traversal is complete (arrows,
O,+/-). - Non-current panels carry
aria-hiddenandinert— virtualized and ambient panels never trap focus. Screen readers follow the graph linearization; the canvas is presentation only.
Embedding API
Hosts embedding PlayerShellComponent get:
<pw-player-shell
[manifest]="manifest"
[viewModeOverride]="'auto'"
(cameraChange)="onCamera($event)">
</pw-player-shell>
auto (default) keeps the manifest-driven behavior — canvas chapters enter
canvas view automatically. panel forces classic panel view (used by the
CMS preview's view selector); canvas enters canvas view whenever the
chapter allows it.
Throttled camera state ({ x, y, zoom } in world units) for hosts and
analytics.
The toolbar's view toggle cycles panel → page → canvas for chapters where each mode is available.