Animate game changes
API
| Hook | Contract |
|---|---|
useLastChange() |
LastChange: { actorPlayerId?; own: boolean; coalesced: boolean; direction?: "forward" | "back"; steps: WasmStepView[]; undo?: WasmLastUndo | null }. Each step's lines are FormattedLines. |
useStepDuration(duration) |
void. duration is milliseconds per change at 1x, a number or (change: LastChange) => number, while the component is mounted. When several components set one, the one mounted last applies. See Step time. |
useBoardTransition() |
BoardTransition: id, fromId, reason, direction, outcome and paired pieces. See Directional animation. |
useTransitionEffect(duration, effect) |
void. Starts a custom animation with (transition, run) and uses the returned cleanup on completion or interruption. run has id, speed, milliseconds(value) and complete(). |
TransitionProvider |
Props: { anchor: (endpoint: TransitionEndpoint) => string | null; duration: number; children: ReactNode }. Optional measured overlays. |
useAnimationAnchor(key) |
{ ref: (element: AnimationElement | null) => void; hidden: boolean }. The element supplies getBoundingClientRect(). Preserve its geometry when hiding its artwork. |
useTransitionOverlays() |
AnimationOverlay[]: key, index, piece, from, to, origin, destination and run. Rectangles have left, top, width, height. |
What changed
useLastChange() gives the last change: its steps, each with actorPlayerId, lines and movements, or the undo that made it. Each line is a FormattedLine: the line's message (Changing the state) in the language the frame shows your game in. Its parts are typed in boardweaver/react reference. lineText joins a line's parts into one string:
const step = useLastChange().steps.at(-1);
const text = (step?.lines ?? []).map(lineText).join(" ");
Draw the parts yourself to style a player or a link. useDraft() gives the lines of the seat's open decision in the same form.
A coalesced change is a jump to a new board, such as a reconnect, a skip to now or an undo. Show its board without animating its steps. Movements describe only a step forward. To animate in either direction, including replay Back, use Directional animation.
Directional animation
useBoardTransition() describes the change from the board shown before to the current useBoard(). Its id grows each time the board on show changes, also on a return to an earlier board. fromId is the id of the board it changes from. While the board stays the same, the id stays the same. When React skipped the fromId board, or the component mounted after it, the hook returns snap with empty pieces.
| Field | Meaning |
|---|---|
reason |
initial, change, playback, stepForward, stepBack, seek, skip, reconnect or undo. |
direction |
forward, back, or null when the reason has no direction. |
outcome |
animate when the recorded movements pair the pieces; otherwise snap, with empty pieces. |
pieces |
Pairs of from and to endpoints. An endpoint is null when the piece is added or removed. |
Endpoint spot |
{ type: "At", spaceId, index } on that endpoint's board, or { type: "Hidden", spaceId }. |
Endpoint piece |
What the viewer sees of the piece on that board. It is null in a hidden group: draw the game's card back. |
- Back and Forward combine every step inside one log row into one transition, the player's own decisions included. Each endpoint belongs to the board it is shown on.
- Back from now also takes the player's draft, and a decision on its way to the server, back with the last row.
- Two pieces with the same face stay apart by their recorded positions. A hidden group gives no single piece and no order. A piece that enters a hidden group and a piece that leaves it are two transitions.
- First load, a seek, a skip to now and a reconnect snap. So do shuffles and every change Boardweaver cannot pair safely.
- A gameplay Undo has reason
undo. An Undo of a draft step plays its recorded movement back, withdirectionback. Any other Undo snaps. - Replay Back changes only the board on show. It does not ask for a gameplay Undo.
Optional animation helpers
Boardweaver chooses no art, path, easing, duration or stagger for an animation: the client does. Use one of these layers, or draw straight from useBoardTransition():
| Layer | Use |
|---|---|
useTransitionEffect(duration, effect) |
Start a custom renderer with Boardweaver's timing and cancellation. Return its cleanup function. |
TransitionProvider |
Measure registered anchors and give overlays, without choosing a renderer or a visual style. |
For useTransitionEffect:
- Pass the whole duration in milliseconds at 1x, with its delays and settling time.
- The effect receives
(transition, run). Pass every duration and delay throughrun.milliseconds(value), so it follows the playback speed. - Each run holds the board until its duration ends, it completes or Boardweaver cancels it. Call
run.complete()to end it early. Other runs and the time in Step time still hold the board. - Return a cleanup that cancels every timer and animation and restores every visual your renderer owns. Boardweaver calls it on completion, a newer board, a step or seek, a speed change, a resize, a hidden tab, a reduced-motion change and unmount.
- A late completion never ends a newer run. A cancelled run stays cancelled, and the next transition starts a new one.
- Boardweaver skips the effect while the player prefers reduced motion or the tab is hidden. The time to read other players' steps still applies.
For measured overlays, wrap the board in TransitionProvider with anchor, duration and children:
anchor(endpoint)maps each endpoint to your own location key, or returnsnullto skip it.- Register each key with
useAnimationAnchor(key), and put itsrefon an element Boardweaver can measure. Whilehiddenistrue, hide that element's artwork but keep its size and place. - Register one group anchor for each hidden hand, also an empty one. A hidden endpoint has no index, so animate it to and from the group.
- Register the places a piece passes through too, such as a selected-card slot. Other players' steps visit them during continuous playback. Forward combines a log row, so a test that only steps Forward from hand to board does not reach those anchors.
useTransitionOverlays() returns one overlay for each pair it measured: origin and destination rectangles in viewport CSS pixels, the paired piece, run, key, index, from and to. Use key as the overlay's React key. Convert the rectangles to your overlay layer's coordinates, and draw the endpoints with your own components.
- A pair with a missing anchor gets no overlay, and its destination stays visible.
- The provider measures every registered anchor on each animation frame, also while idle, and on each resize and scroll. When a destination anchor's size or place changes, the provider ends the run and its overlays.
Hexbloom's animations.tsx, transitions.ts and located-card.tsx (get_example with hexbloom) use this layer for card flights and flips. A canvas, CSS, Web Animations or Motion renderer can use useTransitionEffect without TransitionProvider.
Step time
At 1x, Boardweaver shows each step of other players for at least this time:
| The step has | Time at 1x |
|---|---|
| log lines | 800 ms, plus 55 ms for each character of their text, from 1500 ms to 5000 ms |
| movements and no log lines | 700 ms, plus 100 ms for each movement after the first, up to 1500 ms |
| no log lines and no movements | 250 ms |
An undo stays for 600 ms. The speed divides each time: at 2x, a step of 1800 ms stays for 900 ms.
useTransitionEffect and TransitionProvider hold the board until their run ends (Optional animation helpers). When the client animates a change another way, such as with Motion from useLastChange(), call useStepDuration with the time your animation takes. Boardweaver shows each step for the longer of your time and the time in the table. Without it, the next step can start before your animation ends.
Give every step one time:
useStepDuration(1200);
Or compute the time from your animations. This client slides a piece for 450 ms and flips a piece for 300 ms. It adds 400 ms so the player sees the result:
import type { LastChange } from "boardweaver/react";
const SLIDE_MS = 450;
const FLIP_MS = 300;
const SETTLE_MS = 400;
export const stepTime = (change: LastChange) =>
change.steps.reduce((total, step) => {
const slides = step.movements.filter((movement) => movement.type === "Moved").length;
const flips = step.movements.filter((movement) => movement.type === "Flipped").length;
return total + slides * SLIDE_MS + flips * FLIP_MS + SETTLE_MS;
}, 0);
Then call useStepDuration(stepTime) in the component that draws the board.