Vite + React Canvas Game
A tiny 2D coin-collector game on <canvas> with a requestAnimationFrame loop and keyboard input.
7 steps
Shown with defaults: npm, pip, Node LTS, Python 3.12 and the template's default add-ons.
1. Install and select Node.js with nvm
runtimenvm lets you install several Node.js versions side by side and switch per project. `nvm use` activates it for this shell.
bashnvm install --ltsnvm use --lts- Expected result
- `node -v` prints the selected version.
- Verify
- node -v && npm -v
- OS notes
- macOS/Linux/WSL: install nvm with `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bash`, then reopen the terminal. Windows: use nvm-windows (github.com/coreybutler/nvm-windows) from an elevated terminal, or fnm.
2. Scaffold a Vite + React app
templatecreate-vite copies the React template. --no-interactive skips prompts; the extra '--' is required by npm to forward flags.
bashnpm create vite@latest nvx-game -- --template react-ts --no-interactive- Expected result
- A ./nvx-game folder with index.html, src/ and package.json.
- Verify
- ls nvx-game/src
- OS notes
- create-vite 9 needs Node.js 20.19+ or 22.12+.
3. Enter the project folder
templateThe scaffolder created ./nvx-game. Move into it before installing anything else.
bashcd nvx-game- Expected result
- You are inside ./nvx-game
- Verify
- ls package.json
4. Install dependencies
templateDownload every package listed in package.json into node_modules.
bashnpm install- Expected result
- node_modules/ exists and a lockfile is created.
- Verify
- ls node_modules
5. Write the game loop
templateReplace App with a canvas game: keyboard input, collision detection and a score counter.
Files written by this step: src/App.tsx, src/index.css
src/App.tsximport { useEffect, useRef, useState } from "react"; const W = 640; const H = 400; export default function App() { const canvasRef = useRef<HTMLCanvasElement>(null); const [score, setScore] = useState(0); useEffect(() => { const canvas = canvasRef.current; const context = canvas?.getContext("2d"); if (!context) return; const ctx: CanvasRenderingContext2D = context; const keys: Record<string, boolean> = {}; const player = { x: W / 2, y: H / 2, r: 14, speed: 4 }; const coin = { x: 120, y: 120, r: 8 }; let points = 0; let raf = 0; const down = (e: KeyboardEvent) => { keys[e.key] = true; }; const up = (e: KeyboardEvent) => { keys[e.key] = false; }; window.addEventListener("keydown", down); window.addEventListener("keyup", up); const loop = () => { if (keys.ArrowLeft || keys.a) player.x -= player.speed; if (keys.ArrowRight || keys.d) player.x += player.speed; if (keys.ArrowUp || keys.w) player.y -= player.speed; if (keys.ArrowDown || keys.s) player.y += player.speed; player.x = Math.max(player.r, Math.min(W - player.r, player.x)); player.y = Math.max(player.r, Math.min(H - player.r, player.y)); if (Math.hypot(player.x - coin.x, player.y - coin.y) < player.r + coin.r) { points += 1; setScore(points); coin.x = 20 + Math.random() * (W - 40); coin.y = 20 + Math.random() * (H - 40); } ctx.fillStyle = "#0f172a"; ctx.fillRect(0, 0, W, H); ctx.fillStyle = "#facc15"; ctx.beginPath(); ctx.arc(coin.x, coin.y, coin.r, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = "#38bdf8"; ctx.beginPath(); ctx.arc(player.x, player.y, player.r, 0, Math.PI * 2); ctx.fill(); raf = requestAnimationFrame(loop); }; loop(); return () => { cancelAnimationFrame(raf); window.removeEventListener("keydown", down); window.removeEventListener("keyup", up); }; }, []); return ( <main className="game"> <h1>nvx-game</h1> <p aria-live="polite">Score: {score}</p> <canvas ref={canvasRef} width={W} height={H} aria-label="Game canvas: move with arrow keys or WASD" /> <p>Move with arrow keys or WASD. Collect the coins!</p> </main> ); }src/index.css:root { color-scheme: dark; font-family: system-ui, sans-serif; } body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #020617; color: #e2e8f0; } .game { text-align: center; } canvas { border: 2px solid #334155; border-radius: 12px; max-width: 100%; }- Expected result
- src/App contains the game.
6. Play it in the dev server
templaterun manually · dev serverVite serves the game with instant hot module reload.
bashnpm run dev- Expected result
- http://localhost:5173 shows the canvas; arrow keys move the ball.
- Verify
- curl -I http://localhost:5173
7. Build static files
templateOutputs an optimized static site in dist/ that any static host can serve.
bashnpm run build- Expected result
- dist/index.html and hashed assets.
- Verify
- ls dist