Click on the image below to switch the animation:
Sprite animation has been the heart and soul of most 2d games since the 1980s. Some 8 bit machines had hardware support for sprites. This step-by-step tutorial shows how to create a minimal TypeScript program that loads a PNG spritesheet, animates 20 frames (60×60 pixels) from the first row, and cycles through 5 rows when you click the canvas. The finished project runs in the browser and is dependency-free.

We're using the spritesheet from my game Goldmine.
sprite.ts accordingly.Create an HTML page that hosts a canvas and loads the compiled JavaScript or download it here: index.html. Save this as index.html in your project folder:
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Spritesheet Animator</title>
<style>
canvas { background: #111; display:block; margin:20px auto; image-rendering: pixelated; }
body { color:#ddd; font-family:system-ui, sans-serif; text-align:center; padding:18px; }
</style>
</head>
<body>
<h3>Click the canvas to cycle rows</h3>
<canvas id="c" width="240" height="240" class="example-canvas"></canvas>
<script src="./sprite.js"></script>
</body>
</html>
Copy the code below (or download it here: sprite.ts) and save it as sprite.ts. It is annotated and deliberately small so you can adapt it easily.
// sprite.ts
const CANVAS_ID = 'c';
// Spritesheet configuration
const FRAME_W = 60;
const FRAME_H = 60;
const FRAMES_PER_ROW = 20;
const ROW_COUNT = 5;
// Animation configuration
const FPS = 12;
const MS_PER_FRAME = 1000 / FPS;
const canvas = document.getElementById(CANVAS_ID) as HTMLCanvasElement | null;
if (!canvas) throw new Error('Canvas not found');
const ctx = canvas.getContext('2d');
if (!ctx) throw new Error('2D context not available');
// Optional scale for on-screen crispness
const SCALE = 2;
canvas.width = FRAME_W * SCALE;
canvas.height = FRAME_H * SCALE;
ctx.imageSmoothingEnabled = false;
const img = new Image();
img.src = 'spritesheet.png';
img.onload = () => start();
img.onerror = (e) => console.error('Failed to load spritesheet', e);
let currentFrame = 0;
let currentRow = 0;
let lastTimestamp = 0;
let accumulator = 0;
function update(deltaMs: number) {
accumulator += deltaMs;
while (accumulator >= MS_PER_FRAME) {
accumulator -= MS_PER_FRAME;
currentFrame = (currentFrame + 1) % FRAMES_PER_ROW;
}
}
function draw() {
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
const sx = currentFrame * FRAME_W;
const sy = currentRow * FRAME_H;
const sw = FRAME_W;
const sh = FRAME_H;
const dx = 0;
const dy = 0;
const dw = FRAME_W * SCALE;
const dh = FRAME_H * SCALE;
ctx.drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
}
function loop(ts: number) {
if (!lastTimestamp) lastTimestamp = ts;
const delta = ts - lastTimestamp;
lastTimestamp = ts;
update(delta);
draw();
requestAnimationFrame(loop);
}
function start() {
currentFrame = 0;
currentRow = 0;
lastTimestamp = 0;
accumulator = 0;
canvas.addEventListener('click', () => {
currentRow = (currentRow + 1) % ROW_COUNT;
currentFrame = 0;
});
requestAnimationFrame(loop);
}
spritesheet.png into an Image object and waits for it to finish loading.frameIndex * FRAME_W, and the source Y is rowIndex * FRAME_H.If you have TypeScript installed globally, run:
tsc sprite.ts --target ES6 --lib DOM,ES6
This produces sprite.js in the same folder. Put index.html, sprite.js, and spritesheet.png together and open index.html in your browser.
FRAME_W, FRAME_H, FRAMES_PER_ROW, or ROW_COUNT to match different spritesheets.FPS to speed up or slow down the animation.currentFrame = 0 in the click handler if you want rows to continue where the previous row left off.Interactive fractal graphics zoom