// 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();