Code

Canvas

Mandelbrot Set

Advanced Mandelbrot set renderer with smooth (continuous) iteration colouring — animates a slow zoom into the Seahorse Valley. Wikipedia

// Escape-time algorithm
const mandelbrot = (cx: number, cy: number): number => {
  let zx = 0, zy = 0;
  for (let i = 0; i < maxIter; i++) {
    const zx2 = zx * zx;
    const zy2 = zy * zy;
    if (zx2 + zy2 > 4) {
      // Smooth iteration count (eliminates banding)
      return i + 1 - Math.log2(Math.log2(
        Math.sqrt(zx2 + zy2)
      ));
    }
    zy = 2 * zx * zy + cy;
    zx = zx2 - zy2 + cx;
  }
  return 0; // inside the set
};

// Map screen pixel → complex plane
const render = () => {
  const imageData = ctx.createImageData(W, H);
  const d = imageData.data;
  for (let py = 0; py < H; py++) {
    for (let px = 0; px < W; px++) {
      const cx = (px - W/2) / zoom + centerX;
      const cy = (py - H/2) / zoom + centerY;
      const t = mandelbrot(cx, cy);
      const [r, g, b] = iterToColor(t);
      const i = (py * W + px) * 4;
      d[i]=r; d[i+1]=g; d[i+2]=b; d[i+3]=255;
    }
  }
  ctx.putImageData(imageData, 0, 0);
};

// Animate zoom
const step = () => {
  zoom *= 1.015;
  if (zoom > 80000) zoom = 150; // reset
  render();
  requestAnimationFrame(step);
};
Key Concepts
  • The Mandelbrot set: all complex numbers c for which z²+c does not escape to infinity
  • Escape condition: |z|² > 4 — avoids the expensive Math.sqrt
  • Smooth colouring: i + 1 - log₂(log₂(|z|)) removes integer banding artifacts
  • All rendering goes through ImageData — writing to a Uint8ClampedArray is much faster than per-pixel fillRect
  • Zoom: divide screen coordinates by zoom factor to map to the complex plane
  • For interactive zooming: move render() to a Web Worker to keep the UI responsive