// Update canvas size on resize
function resizeCanvas() {
canvas.width = window.innerWidth * pixelRatio;
canvas.height = window.innerHeight * pixelRatio;
ctx.scale(pixelRatio, pixelRatio);
drawMandelbrot();
}
// Initialize
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
// Reset view to initial bounds
resetButton.addEventListener('click', () => {
zoom = 1.0;
offsetX = -0.75;
offsetY = 0.0;
drawMandelbrot();
});
// Handle mouse wheel zoom
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
const centerX = (mouseX / canvas.width) * 2 - 1;
const centerY = (mouseY / canvas.height) * 2 - 1;
// Calculate new zoom level
const zoomFactor = e.deltaY > 0 ? 1.2 : 0.8;
zoom *= zoomFactor;
// Adjust offset to keep cursor at same point
offsetX += (centerX * (zoomFactor - 1)) / zoom;
offsetY += (centerY * (zoomFactor - 1)) / zoom;
drawMandelbrot();
});
// Handle drag
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
canvas.addEventListener('mousedown', (e) => {
if (e.button === 0) { // Left mouse button
isDragging = true;
lastMouseX = e.clientX;
lastMouseY = e.clientY;
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
const rect = canvas.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// Calculate how far we've moved
const deltaX = mouseX - lastMouseX;
const deltaY = mouseY - lastMouseY;
// Update offsets
offsetX -= deltaX / canvas.width;
offsetY -= deltaY / canvas.height;
lastMouseX = mouseX;
lastMouseY = mouseY;
drawMandelbrot();
}
});
canvas.addEventListener('mouseup', () => {
isDragging = false;
});
canvas.addEventListener('mouseleave', () => {
isDragging = false;
});
// Draw the Mandelbrot set
function drawMandelbrot() {
const width = canvas.width;
const height = canvas.height;
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
// Map canvas coordinates to complex plane
const realMin = offsetX - zoom;
const realMax = offsetX + zoom;
const imagMin = offsetY - zoom;
const imagMax = offsetY + zoom;
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
// Map pixel to complex coordinate
const real = realMin + (x / width) * (realMax - realMin);
const imag = imagMin + (y / height) * (imagMax - imagMin);
// Compute Mandelbrot iteration
let cReal = real;
let cImag = imag;
let zReal = 0;
let zImag = 0;
let iteration = 0;
let magnitudeSquared = 0;
while (iteration < maxIterations && magnitudeSquared < 4) {
const tempReal = zReal * zReal - zImag * zImag + cReal;
const tempImag = 2 * zReal * zImag + cImag;
zReal = tempReal;
zImag = tempImag;
magnitudeSquared = zReal * zReal + zImag * zImag;
iteration++;
}
// Color based on escape time
if (iteration === maxIterations) {
// Inside set - use a distinct color
data[(y * width + x) * 4] = 0; // R
data[(y * width + x) * 4 + 1] = 0; // G
data[(y * width + x) * 4 + 2] = 0; // B
data[(y * width + x) * 4 + 3] = 255; // A
} else {
// Outside set - smooth gradient
const normalizedIteration = iteration / maxIterations;
const hue = 255 * normalizedIteration;
const saturation = 100;
const lightness = 50;
// Convert HSL to RGB
const h = hue / 360;
const s = saturation / 100;
const l = lightness / 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h * 6) % 2 - 1));
const m = l - c / 2;
let r, g, b;
if (h < 1/6) {
r = c;
g = x;
b = 0;
} else if (h < 2/6) {
r = x;
g = c;
b = 0;
} else if (h < 3/6) {
r = 0;
g = c;
b = x;
} else if (h < 4/6) {
r = 0;
g = x;
b = c;
} else if (h < 5/6) {
r = x;
g = 0;
b = c;
} else {
r = c;
g = 0;
b = x;
}
r = (r + m) * 255;
g = (g + m) * 255;
b = (b + m) * 255;
data[(y * width + x) * 4] = Math.floor(r);
data[(y * width + x) * 4 + 1] = Math.floor(g);
data[(y * width + x) * 4 + 2] = Math.floor(b);
data[(y * width + x) * 4 + 3] = 255;
}
}
}
ctx.putImageData(imageData, 0, 0);
}
// Increase iterations as zoom increases for better detail
window.addEventListener('resize', () => {
// Adjust maxIterations based on zoom level
maxIterations = Math.min(1000, Math.max(100, Math.floor(100 + 100 * Math.log(zoom))));
clearCanvas();
drawMandelbrot();
});
// Initial render
clearCanvas();
drawMandelbrot();