The client

v3 · Preview

Framework v3 is an opt-in preview available to everyone. New games use the stable framework by default.

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, with direction back. 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 through run.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 returns null to skip it.
  • Register each key with useAnimationAnchor(key), and put its ref on an element Boardweaver can measure. While hidden is true, 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.

Need a hand with this?

Join our Discord