Skip to content
GitHub

Sprite Canvas

A canvas renderer for single sprite-sheets

import { SpriteCanvas } from "@/components/ui/pxl/sprite-canvas";
export default function SpriteCanvasPreview() {
return (
<div className="w-full min-h-23 flex items-center justify-center">
<SpriteCanvas
src="https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png"
animation="Idle"
size="lg"
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
/>
</div>
);
}
pnpm dlx shadcn@latest add pxl-ui/registry/sprite-canvas
import { SpriteCanvas } from "@/components/ui/pxl/sprite-canvas"
<SpriteCanvas
src="https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png"
animation="Idle"
size="lg"
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
/>

Use the size prop to change the size of the button.

ExtraSmall

Small

Medium

Large

ExtraLarge

import { Fragment } from "react";
import { SpriteCanvas } from "@/components/ui/pxl/sprite-canvas";
import type { SpriteSize } from "@/lib/pxl/sprite";
export default function Sizes() {
const sizes: Record<SpriteSize, string> = {
xs: "ExtraSmall",
sm: "Small",
md: "Medium",
lg: "Large",
xl: "ExtraLarge",
};
return (
<div className="w-full min-h-92 flex flex-col gap-2.5 items-start justify-center">
<div>
{Object.entries(sizes).map(([size, label]) => (
<Fragment key={size}>
<h2>{label}</h2>
<SpriteCanvas
src="https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png"
animation="Idle"
size={size as SpriteSize}
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
/>
</Fragment>
))}
</div>
</div>
);
}

You can pass multiple urls to compose a multi-layered sprite

import { SpriteCanvas } from "@/components/ui/pxl/sprite-canvas";
export default function MultiLayer() {
return (
<div className="w-full min-h-23 flex items-center justify-center">
<SpriteCanvas
src={[
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/bodies/human/male/ivory/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/bodyParts/hair/male/long/blonde2/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/bodyParts/eyes/male/blue/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/bodyParts/facialHair/male/beard/blonde2/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/clothes/torso/male/shirt/formal/white/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/clothes/legs/male/pants/pants/blue/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/clothes/belt/male/leather/leather/Universal.png",
"https://raw.githubusercontent.com/central-factory/Universal-LPC-spritesheet/refs/heads/new-api/assets2/characters/humanoids/clothes/feet/male/shoes/brown/Universal.png"
]}
size="lg"
animation="Idle_Down"
atlas={{
frames: {
"Idle_Down_0": { frame: { x: 0, y: 640, w: 64, h: 64 } },
"Idle_Down_1": { frame: { x: 64, y: 640, w: 64, h: 64 } },
"Idle_Down_2": { frame: { x: 128, y: 640, w: 64, h: 64 } },
"Idle_Down_3": { frame: { x: 192, y: 640, w: 64, h: 64 } },
"Idle_Down_4": { frame: { x: 256, y: 640, w: 64, h: 64 } },
"Idle_Down_5": { frame: { x: 320, y: 640, w: 64, h: 64 } },
"Idle_Down_6": { frame: { x: 384, y: 640, w: 64, h: 64 } },
"Idle_Down_7": { frame: { x: 448, y: 640, w: 64, h: 64 } },
"Idle_Down_8": { frame: { x: 512, y: 640, w: 64, h: 64 } },
},
meta: { frameTags: [{ name: "Idle_Down", from: 0, to: 8 }] },
}}
/>
</div>
);
}

A custom choice animation for sprite canvas

import { useRef } from "react";
import { Button } from "@/components/ui/pxl/button";
import {
SpriteCanvas,
type SpriteCanvasHandle,
} from "@/components/ui/pxl/sprite-canvas";
import { choiceEffect } from "@/lib/pxl/sprite-animations/choice";
export default function ChoiceExample() {
const ref = useRef<SpriteCanvasHandle>(null);
function choose() {
if (!ref.current) {
return;
}
ref.current.play("Choice", undefined, () => console.log("end"), {
src: "/sprites/clucking_chicken.png",
});
}
return (
<div className="flex flex-col gap-4 items-center">
<SpriteCanvas
animation="Idle"
ref={ref}
src="https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png"
size="lg"
effects={[choiceEffect]}
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
/>
<Button onClick={choose}>Play</Button>
</div>
);
}
pnpm dlx shadcn@latest add pxl-ui/registry/sprite-animation-choice

A custom transition animation for sprite canvas

import { useCallback, useRef, useState } from "react";
import { Button } from "@/components/ui/pxl/button";
import {
SpriteCanvas,
type SpriteCanvasHandle,
} from "@/components/ui/pxl/sprite-canvas";
import { transitionEffect } from "@/lib/pxl/sprite-animations/transition";
const sprites = [
"https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png",
"https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/clucking_chicken.png",
];
export default function TransitionExample() {
const [activeIndex, setActiveIndex] = useState(0);
const ref = useRef<SpriteCanvasHandle>(null);
const play = useCallback((direction: 1 | -1) => {
if (!ref.current) {
return;
}
const nextIndex = activeIndex === 0 ? 1 : 0;
ref.current.play("Transition", undefined, () => console.log("end"), {
src: sprites[nextIndex],
direction,
});
setActiveIndex(nextIndex);
}, [activeIndex])
return (
<div className="flex flex-col items-center gap-4">
<SpriteCanvas
ref={ref}
src={sprites[0]}
size="lg"
effects={[transitionEffect]}
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
animation="Idle"
/>
<div className="flex flex-row gap-6">
<Button size="sm" onClick={() => play(1)}>PREV</Button>
<Button size="sm" onClick={() => play(-1)}>NEXT</Button>
</div>
</div>
);
}
pnpm dlx shadcn@latest add pxl-ui/registry/sprite-animation-transition

A custom evolution animation for sprite canvas

import { useRef } from "react";
import { Button } from "@/components/ui/pxl/button";
import {
SpriteCanvas,
type SpriteCanvasHandle,
} from "@/components/ui/pxl/sprite-canvas";
import { evolutionEffect } from "@/lib/pxl/sprite-animations/evolution";
export default function EvolutionExample() {
const ref = useRef<SpriteCanvasHandle>(null);
function evolve() {
if (!ref.current) {
return;
}
ref.current.play("Evolution", undefined, () => console.log("end"), {
src: "https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/clucking_chicken.png",
});
}
return (
<div className="flex flex-col gap-4 items-center">
<SpriteCanvas
animation="Idle"
ref={ref}
src="https://raw.githubusercontent.com/pxl-ui/registry/main/app/public/sprites/tiny_chick.png"
size="lg"
effects={[evolutionEffect]}
atlas={{
frames: {
"0": { frame: { x: 0, y: 0, w: 16, h: 16 }, },
"1": { frame: { x: 16, y: 0, w: 16, h: 16 }, },
"2": { frame: { x: 32, y: 0, w: 16, h: 16 }, },
"3": { frame: { x: 48, y: 0, w: 16, h: 16 }, },
},
meta: { frameTags: [ { name: "Idle", from: 0, to: 3, }, ], },
}}
/>
<Button onClick={evolve}>Play</Button>
</div>
);
}
pnpm dlx shadcn@latest add pxl-ui/registry/sprite-animation-evolution