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.
Mouse Drawing App
Intermediate Interactive paint canvas — smooth drawing and erasing via mouse and touch events, with brush colour and size controls. MDN Reference
// Smooth drawing with mousemove
let drawing = false, lastX = 0, lastY = 0;
canvas.addEventListener('mousedown', e => {
drawing = true;
({ lastX, lastY } = getPos(e));
});
canvas.addEventListener('mousemove', e => {
if (!drawing) return;
const { x, y } = getPos(e);
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = brushSize;
ctx.strokeStyle = brushColor;
ctx.stroke();
[lastX, lastY] = [x, y];
});
canvas.addEventListener('mouseup', () => drawing = false);
canvas.addEventListener('mouseleave', () => drawing = false);
// Scale mouse coords for CSS-scaled canvas
const getPos = (e: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
return {
x: (e.clientX - rect.left) * (canvas.width / rect.width),
y: (e.clientY - rect.top) * (canvas.height / rect.height),
};
};
// Eraser: draw white OR use destination-out
ctx.globalCompositeOperation = 'destination-out';
ctx.strokeStyle = 'rgba(0,0,0,1)';
// reset after:
ctx.globalCompositeOperation = 'source-over';
// Always remove listeners on destroy!
ngOnDestroy() {
canvas.removeEventListener('mousedown', startDraw);
canvas.removeEventListener('mousemove', draw);
}Key Concepts
- Track
mousedown/mousemove/mouseupfor draw state - Scale coordinates:
(clientX - rect.left) × (canvas.width / rect.width) lineCap = 'round'+lineJoin = 'round'for smooth strokes- Eraser with
globalCompositeOperation = 'destination-out' - Support touch with
touchstart/touchmove+e.preventDefault() - Always remove event listeners in
ngOnDestroyto prevent memory leaks