Skip to main content
Version: 0.x.x

Spritesheet & Animation

A spritesheet packs many frames into a single image: one network request, one decode, and on WebGL a single texture for all frames. The React package supports it in two ways:

  • Fixed frame — pass a sprite rect on any <Image> item
  • Animation — the <Sprite> component flips through frames at a given fps

SpriteSheet (the frame calculator) and the sprite types are re-exported from @canvas-tile-engine/react, so no direct core import is needed.

Fixed Frames with <Image>

Every ImageItem accepts an optional sprite property — a pixel rectangle inside the sheet. Use SpriteSheet to compute it from a grid position:

import { CanvasTileEngine, SpriteSheet } from "@canvas-tile-engine/react";

// A 432x512 sheet with 3 columns x 4 rows of 144x128 frames
const sheet = new SpriteSheet({ frameWidth: 144, frameHeight: 128, columns: 3 });

<CanvasTileEngine.Image
items={{ x: 2, y: 3, size: 1.5, img: dragonSheet, sprite: sheet.frame(0, 2) }}
layer={2}
/>;

See the core Spritesheet guide for SpriteSheet options (margin, spacing, frameByIndex, ...).

Animation with <Sprite>

<Sprite> draws its items cropped to the current animation frame and re-renders at the animation's fps. All items of one <Sprite> share the animation and flip in sync.

import { useMemo, useState } from "react";
import { CanvasTileEngine, SpriteSheet, useCanvasTileEngine } from "@canvas-tile-engine/react";

const sheet = new SpriteSheet({ frameWidth: 144, frameHeight: 128, columns: 3 });

function FlyingDragon({ img }: { img: HTMLImageElement }) {
const engine = useCanvasTileEngine();
const [playing, setPlaying] = useState(true);

// Keep items and frames referentially stable
const item = useMemo(() => ({ x: 5, y: 3, size: 2, img }), [img]);
const frames = useMemo(() => sheet.framesInRow(0, 0, 2), []);

return (
<CanvasTileEngine
engine={engine}
config={config}
renderer={new RendererCanvas()}
onClick={() => setPlaying((prev) => !prev)}
>
<CanvasTileEngine.Sprite items={item} frames={frames} fps={8} playing={playing} />
</CanvasTileEngine>
);
}

Props:

PropTypeDefaultDescription
itemsImageItem | ImageItem[]RequiredItems to draw. Cloned internally — your objects are never mutated.
framesSpriteRect[]RequiredAnimation frames in play order, e.g. sheet.framesInRow(0, 0, 4).
fpsnumberRequiredPlayback speed in frames per second.
loopbooleantrueRestart from the first frame after the last one.
playingbooleantrueToggle playback. Re-enabling restarts from the first frame.
layernumber1Rendering layer.
onComplete() => void-Fired when a non-looping animation reaches its last frame.
Referential stability

Like <Image>, the items and frames props are compared by reference. Create them with useMemo/useState — an inline literal re-registers the draw callback (and restarts the animation) on every render.

One <Sprite> per animation

Items inside a single <Sprite> animate in sync (one internal animator, one render per tick). Use separate <Sprite> components for independently-timed animations.

// One-shot effect: remove from the tree when finished
{exploding && (
<CanvasTileEngine.Sprite
items={explosionItem}
frames={explosionFrames}
fps={12}
loop={false}
onComplete={() => setExploding(false)}
/>
)}
Full example

See examples/react/spritesheet in the repository for a complete runnable demo.

pnpm dev:example --example=react-spritesheet