Scrollytelling

Waypoint Stage

Place each passage of content by coordinate on an oversized canvas, and let scroll drive a camera that pans, zooms, and rotates along a waypoint path for a Prezi-style spatial narrative.

This is a WebberUI Pro component

The install command below needs a licence to run; without one it returns 401.

How to install Pro components →See the plans →

Bring this into your project

Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.

npx webberui@latest add "https://webberui.com/r/waypoint-stage.en.json?t=<install token>"

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

1
0.25
0.72
Active edited usageDefault props
<WaypointStage/>

The complete example includes the preview layout and data. Install the component first, then paste this file into your project.

Installation

npx webberui@latest add "https://webberui.com/r/waypoint-stage.en.json?t=<install token>"

Or, once registries are configured in components.json, install it as @webberui/waypoint-stage.

Usage

Wrap a number of Waypoints in a WaypointStage; each waypoint declares its coordinates on the canvas with x / y, and its zoom / rotate decide the composition the camera arrives at. Scrolling drives the camera to fly to them in order.

import { Waypoint, WaypointStage } from "@/components/ui/waypoint-stage";

<WaypointStage>
  <Waypoint x={0} y={0} label="Opening">
    <h2 className="text-4xl font-bold">Welcome</h2>
  </Waypoint>
  <Waypoint x={800} y={-320} zoom={1.4} rotate={-8} label="Key point">
    <p className="max-w-sm">Fly here to zoom in on a tilted composition.</p>
  </Waypoint>
  <Waypoint x={-360} y={420} zoom={1.2} rotate={6} label="Closing">
    <p>Then turn to somewhere else.</p>
  </Waypoint>
</WaypointStage>

By default the window is the scroll container and the stage is pinned at h-screen. If it sits inside a nested overflow-y-auto container, pass that container's ref to container and override the stage height with stageClassName to the container's visible height (such as h-[300px]).

Props

WaypointStage

PropTypeDefaultDescription
childrenReactNode—Put Waypoint children here
pacenumber1Scroll length of each leg (in units of the pinned stage height)
dwellnumber0.25Proportion of the leg spent lingering after arriving at each waypoint (0–0.49)
zoomOutnumber0.72How far the camera pulls back between two waypoints (1 means no pull-back)
smoothbooleantrueWhether to smooth the camera work with a spring
showPathbooleantrueWhether to draw the dashed path connecting the waypoints
showNavbooleantrueWhether to show the clickable navigation dots that jump between waypoints
gridbooleantrueWhether to lay down the faint dot-grid texture
containerRefObject<HTMLElement | null>—Ref of the nested scroll container; defaults to the window as scroll container
stageClassNamestring"h-screen"Height of the pinned stage container
classNamestring—The outer scroll track container

Waypoint

PropTypeDefaultDescription
xnumber—X coordinate of the waypoint's center on the canvas (px)
ynumber—Y coordinate of the waypoint's center on the canvas (px)
zoomnumber1Zoom when the camera arrives; >1 zooms in, <1 zooms out
rotatenumber0Rotation angle of this waypoint on the canvas (degrees); it is straightened on arrival
labelstring—Name used by the navigation dots and assistive technology
childrenReactNode—Waypoint content

How it works

  • The camera is implemented in pure CSS transform (translate + scale + rotate, with the origin anchored to the viewport center) — no WebGL needed. Content is centered on its own coordinates, so the camera arrives with it straightened and centered.
  • The transition uses a "pull back first, then push in" keyframe; zoomOut controls how far it pulls back midway, and dwell controls how long it lingers after arriving.
  • The track height is computed automatically from the number of waypoints and pace: more waypoints and a larger pace mean a longer scroll distance and more leisurely camera work.
  • For nested scrolling, always pass both container and a matching stageClassName, so useScroll correctly treats that container as its scroll source.

Accessibility

  • When the user has "reduce motion" enabled at the system level, the camera work is disabled entirely and the waypoints are instead laid out in order as static vertical passages, avoiding the discomfort that sweeping zoom and rotation can cause.
  • The navigation dots are focusable buttons carrying aria-label and aria-current, operable with the keyboard and jumping smoothly to their waypoint.
  • The stage also carries an sr-only ordered list for assistive technology to announce, decoupled from the animated visuals.
  • The ResizeObserver and the springs are all cleaned up on unmount by Motion and the effects.