134 lines
4.8 KiB
HTML
134 lines
4.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Interaktives Mandelbrot</title>
|
|
<style>
|
|
body { margin: 0; background: #111; color: white; font-family: sans-serif; overflow: hidden; }
|
|
canvas { display: block; cursor: crosshair; }
|
|
.controls { position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); padding: 15px; border-radius: 8px; pointer-events: none; }
|
|
kbd { background: #444; padding: 2px 5px; border-radius: 3px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="controls">
|
|
<h2>Mandelbrot Explorer</h2>
|
|
<p><kbd>Links-Klick</kbd> Zoom In</p>
|
|
<p><kbd>Rechts-Klick</kbd> Zoom Out</p>
|
|
<small id="status">Berechne...</small>
|
|
</div>
|
|
<canvas id="mandelCanvas"></canvas>
|
|
|
|
<script>
|
|
const canvas = document.getElementById('mandelCanvas');
|
|
const ctx = canvas.getContext('2d');
|
|
const status = document.getElementById('status');
|
|
|
|
let width, height;
|
|
// Start-Koordinaten des Mandelbrot-Sets
|
|
let zoom = 1.0;
|
|
let centerX = -0.5;
|
|
let centerY = 0;
|
|
let maxIter = 150;
|
|
|
|
function resize() {
|
|
width = canvas.width = window.innerWidth;
|
|
height = canvas.height = window.innerHeight;
|
|
draw();
|
|
}
|
|
|
|
function draw() {
|
|
const imgData = ctx.createImageData(width, height);
|
|
const data = imgData.data;
|
|
|
|
// Logik für den Bildausschnitt
|
|
const aspect = width / height;
|
|
const viewWidth = 4 / zoom;
|
|
const viewHeight = viewWidth / aspect;
|
|
|
|
const xMin = centerX - viewWidth / 2;
|
|
const yMin = centerY - viewHeight / 2;
|
|
|
|
for (let py = 0; py < height; py++) {
|
|
for (let px = 0; px < width; px++) {
|
|
let c_re = xMin + (px / width) * viewWidth;
|
|
let c_im = yMin + (py / height) * viewHeight;
|
|
|
|
let x = 0, y = 0, x2 = 0, y2 = 0, iter = 0;
|
|
|
|
while (x2 + y2 <= 4 && iter < maxIter) {
|
|
y = 2 * x * y + c_im;
|
|
x = x2 - y2 + c_re;
|
|
x2 = x * x;
|
|
y2 = y * y;
|
|
iter++;
|
|
}
|
|
|
|
const pixelIndex = (py * width + px) * 4;
|
|
if (iter === maxIter) {
|
|
data[pixelIndex] = data[pixelIndex+1] = data[pixelIndex+2] = 0; // Schwarz
|
|
} else {
|
|
// Dynamische Färbung (Hübsch & Schnell)
|
|
const hue = (iter / maxIter) * 360;
|
|
const rgb = hslToRgb(hue / 360, 0.8, 0.5);
|
|
data[pixelIndex] = rgb[0];
|
|
data[pixelIndex+1] = rgb[1];
|
|
data[pixelIndex+2] = rgb[2];
|
|
}
|
|
data[pixelIndex+3] = 255; // Alpha
|
|
}
|
|
}
|
|
ctx.putImageData(imgData, 0, 0);
|
|
status.textContent = `Zoom: ${zoom.toFixed(2)}x | Iterationen: ${maxIter}`;
|
|
}
|
|
|
|
// Hilfsfunktion für Farben
|
|
function hslToRgb(h, s, l) {
|
|
let r, g, b;
|
|
if (s === 0) r = g = b = l;
|
|
else {
|
|
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
const p = 2 * l - q;
|
|
const hue2rgb = (p, q, t) => {
|
|
if (t < 0) t += 1; if (t > 1) t -= 1;
|
|
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
if (t < 1/2) return q;
|
|
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
return p;
|
|
};
|
|
r = hue2rgb(p, q, h + 1/3);
|
|
g = hue2rgb(p, q, h);
|
|
b = hue2rgb(p, q, h - 1/3);
|
|
}
|
|
return [r * 255, g * 255, b * 255];
|
|
}
|
|
|
|
// Interaktion
|
|
canvas.addEventListener('mousedown', (e) => {
|
|
const aspect = width / height;
|
|
const viewWidth = 4 / zoom;
|
|
const viewHeight = viewWidth / aspect;
|
|
|
|
// Klick-Position in komplexe Koordinaten umrechnen
|
|
const xClick = (centerX - viewWidth / 2) + (e.clientX / width) * viewWidth;
|
|
const yClick = (centerY - viewHeight / 2) + (e.clientY / height) * viewHeight;
|
|
|
|
if (e.button === 0) { // Links: Rein
|
|
zoom *= 2;
|
|
maxIter += 50; // Mehr Details beim Zoomen
|
|
} else if (e.button === 2) { // Rechts: Raus
|
|
zoom /= 2;
|
|
maxIter = Math.max(100, maxIter - 50);
|
|
}
|
|
|
|
centerX = xClick;
|
|
centerY = yClick;
|
|
draw();
|
|
});
|
|
|
|
canvas.oncontextmenu = (e) => e.preventDefault();
|
|
window.addEventListener('resize', resize);
|
|
resize();
|
|
</script>
|
|
</body>
|
|
</html> |