Code
Canvas
Basic Shapes
fillRect, strokeRect, arc, closePath and roundRect — the core drawing primitives.
Colors & Gradients
Linear, radial and conic gradients, drop shadows, and globalAlpha transparency.
Text Rendering
fillText, strokeText, font styles, textAlign, textBaseline, and measureText.
Transformations
translate, rotate, scale, setTransform and the save/restore state stack.
Bezier Paths
Quadratic and cubic Bézier curves, Path2D reuse, star polygons, and arcTo.
Animation Loop
requestAnimationFrame-driven bouncing balls with gravity, friction, and motion-blur trails.
Mouse Drawing
Interactive paint application — smooth draw/erase with brush colour and size controls.
Compositing Modes
All 16 globalCompositeOperation modes — from source-over to multiply and xor.
Pixel Manipulation
ImageData filters — grayscale, invert, sepia, brightness boost, and pixelate.
Particle System
Physics-based particles with buoyancy, turbulence, colour lifecycle, and additive blending.
Fractal Tree
Animated recursive fractal tree — dynamic branching angle with brown-to-green colour transition.
Conway's Game of Life
Cellular automaton on a toroidal grid — pause, resume, and randomise the population.
Flow Field
1,200 particles following a dynamic vector field built from layered sine/cosine noise.
Mandelbrot Set
Mandelbrot set with smooth colouring, auto-zooming into the Seahorse Valley.
Generative Art
Algorithmic tile grid — noise-driven hue and rotation with layered concentric ring overlays.
Conway's Game of Life
Advanced Classic cellular automaton — toroidal grid with typed arrays for efficient neighbour counting. Wikipedia
// Flat typed array — fast memory layout
const grid = new Uint8Array(cols * rows);
const next = new Uint8Array(cols * rows);
// Toroidal index (wraps around edges)
const idx = (x: number, y: number) =>
((y + rows) % rows) * cols +
((x + cols) % cols);
// Conway's rules:
// Alive cell: survives with 2 or 3 neighbours
// Dead cell: born with exactly 3 neighbours
const step = () => {
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
let n = 0;
for (let dy = -1; dy <= 1; dy++)
for (let dx = -1; dx <= 1; dx++)
if (dx || dy) n += grid[idx(x+dx, y+dy)];
const alive = grid[idx(x, y)];
next[idx(x, y)] = alive
? (n === 2 || n === 3 ? 1 : 0)
: (n === 3 ? 1 : 0);
}
}
grid.set(next); // swap buffers
};
// Draw
const draw = () => {
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, W, H);
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
if (grid[idx(x, y)]) {
ctx.fillStyle = '#38bdf8';
ctx.fillRect(x*cs+1, y*cs+1, cs-1, cs-1);
}
}
}
};Key Concepts
Uint8Array— 1 byte per cell, cache-friendly flat layout vs. nested arrays- Double buffer: compute into
next, thengrid.set(next)— never read and write the same array - Toroidal wrap:
(x + cols) % colsmakes the grid wrap at edges - Count all 8 neighbours with a nested
dx/dyloop, skipdx===0 && dy===0 - Use
setInterval(notrAF) for a fixed generation rate independent of frame rate - Interesting patterns: Glider, R-pentomino, Gosper Glider Gun