Part 1 - Introduction to HTML5 Canvas & The 2D Context
The HTML5 <canvas> element is a high-performance bitmap display surface. It acts as an empty digital canvas upon which you can draw 2D shapes, sprites, animations, and entire interactive games purely through JavaScript.
Unlike DOM-based graphics, canvas renders pixels directly. This delivers the blistering speed needed for 60fps arcade games, particle simulations, and visual data engines.
<!-- HTML -->
<canvas id="gameCanvas" width="800" height="450"></canvas>
// JavaScript Setup
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d'); // Obtain the 2D rendering context
Part 2 - Drawing Primitives: Shapes, Paths & Text
The 2D context provides methods for drawing geometric primitives:
// 1. Clear previous frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. Draw a filled rectangle (Player)
ctx.fillStyle = '#0ea5e9'; // Theme primary color
ctx.fillRect(50, 50, 40, 40); // x, y, width, height
// 3. Draw an outlined circle (Ball / Projectile)
ctx.beginPath();
ctx.arc(200, 150, 20, 0, Math.PI * 2); // x, y, radius, startAngle, endAngle
ctx.fillStyle = '#ef4444';
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.stroke();
// 4. Draw Score Text
ctx.fillStyle = '#f8fafc';
ctx.font = 'bold 20px Inter, sans-serif';
ctx.fillText('Score: 100', 20, 35);
Part 3 - The Game Loop Architecture
Every video game runs on an infinite loop that updates physics, processes inputs, and renders frames:
function gameLoop(timestamp) {
// 1. Process User Input
handleInput();
// 2. Update Game State (Move player, check collisions)
update();
// 3. Draw the new frame
draw();
// 4. Request next frame synchronized with monitor refresh rate
requestAnimationFrame(gameLoop);
}
// Kick off the loop
requestAnimationFrame(gameLoop);
Part 4 - Capturing Keyboard & Touch Input
Smooth controls require tracking which keys are currently held down:
const keys = {};
window.addEventListener('keydown', (e) => {
keys[e.key] = true;
});
window.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
function handleInput() {
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;
}
Part 5 - Collision Detection: AABB (Axis-Aligned Bounding Box)
To detect when two rectangular sprites touch, compare their boundaries:
function checkCollision(rect1, rect2) {
return (
rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y
);
}
Part 6 - Complete Playable Mini-Game: Arcade Meteor Dodger
Here is a complete, self-contained mini-game you can run in your browser right now:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const player = { x: 380, y: 380, width: 30, height: 30, speed: 6, color: '#0ea5e9' };
let meteors = [];
let score = 0;
let gameOver = false;
function spawnMeteor() {
if (gameOver) return;
meteors.push({
x: Math.random() * (canvas.width - 25),
y: -30,
width: 25,
height: 25,
speed: 3 + Math.random() * 4,
color: '#f43f5e'
});
}
setInterval(spawnMeteor, 600);
function update() {
if (gameOver) return;
// Movement
if (keys['ArrowLeft'] && player.x > 0) player.x -= player.speed;
if (keys['ArrowRight'] && player.x + player.width < canvas.width) player.x += player.speed;
// Meteors update
for (let i = meteors.length - 1; i >= 0; i--) {
const m = meteors[i];
m.y += m.speed;
// Collision Check
if (checkCollision(player, m)) {
gameOver = true;
}
// Screen boundary
if (m.y > canvas.height) {
meteors.splice(i, 1);
score += 10;
}
}
}
function draw() {
// Clear screen
ctx.fillStyle = '#0b1120';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Player
ctx.fillStyle = player.color;
ctx.fillRect(player.x, player.y, player.width, player.height);
// Draw Meteors
meteors.forEach(m => {
ctx.fillStyle = m.color;
ctx.fillRect(m.x, m.y, m.width, m.height);
});
// Score
ctx.fillStyle = '#f8fafc';
ctx.font = '18px Inter, sans-serif';
ctx.fillText(`Score: ${score}`, 20, 30);
if (gameOver) {
ctx.fillStyle = '#ef4444';
ctx.font = 'bold 36px Outfit, sans-serif';
ctx.fillText('GAME OVER', 290, 220);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);