Created
August 9, 2026 18:57
-
-
Save phkahler/09dca2579cc5397e9fccef7e48d857b0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Animated Julia Fractal Explorer</title> | |
| <style> | |
| body, html { | |
| margin: 0; | |
| padding: 0; | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| background-color: #000; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100vw; | |
| height: 100vh; | |
| } | |
| #ui-overlay { | |
| position: absolute; | |
| top: 20px; | |
| left: 20px; | |
| color: #fff; | |
| background: rgba(0, 0, 0, 0.7); | |
| padding: 15px 20px; | |
| border-radius: 8px; | |
| backdrop-filter: blur(5px); | |
| pointer-events: none; | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| h1 { | |
| margin: 0 0 10px 0; | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| letter-spacing: 0.5px; | |
| } | |
| .data-line { | |
| font-family: 'Courier New', Courier, monospace; | |
| font-size: 0.9rem; | |
| margin: 4px 0; | |
| color: #00ffcc; | |
| } | |
| .instructions { | |
| margin-top: 12px; | |
| font-size: 0.75rem; | |
| color: #aaa; | |
| line-height: 1.4; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div id="ui-overlay"> | |
| <h1>Julia Fractal Animation</h1> | |
| <div class="data-line">Z<sub>n+1</sub> = Z<sub>n</sub><sup>2</sup> + C</div> | |
| <div class="data-line" id="c-value">C = 0.00000 + 0.00000i</div> | |
| <div class="instructions"> | |
| Orbiting boundary of the Mandelbrot Set | |
| </div> | |
| </div> | |
| <canvas id="glcanvas"></canvas> | |
| <!-- Vertex Shader --> | |
| <script id="vs" type="x-shader/x-vertex"> | |
| attribute vec2 position; | |
| void main() { | |
| gl_Position = vec4(position, 0.0, 1.0); | |
| } | |
| </script> | |
| <!-- Fragment Shader --> | |
| <script id="fs" type="x-shader/x-fragment"> | |
| precision highp float; | |
| uniform vec2 u_resolution; | |
| uniform vec2 u_c; | |
| const int MAX_ITERATIONS = 200; | |
| // Cosine based palette generators by Inigo Quilez | |
| vec3 palette( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) { | |
| return a + b*cos( 6.28318*(c*t+d) ); | |
| } | |
| void main() { | |
| // Map coordinates to complex plane range centered on screen | |
| vec2 st = (gl_FragCoord.xy - 0.5 * u_resolution.xy) / min(u_resolution.x, u_resolution.y); | |
| vec2 z = st * 3.0; | |
| float n = 0.0; | |
| float max_i = float(MAX_ITERATIONS); | |
| bool escaped = false; | |
| for (int i = 0; i < MAX_ITERATIONS; i++) { | |
| // Z^2 + C calculation | |
| float x = (z.x * z.x - z.y * z.y) + u_c.x; | |
| float y = (2.0 * z.x * z.y) + u_c.y; | |
| z = vec2(x, y); | |
| if (dot(z, z) > 4.0) { | |
| // if (x*x > 4.0) { | |
| n = float(i); | |
| escaped = true; | |
| break; | |
| } | |
| } | |
| // Smooth coloring algorithm | |
| if (escaped) { | |
| float log_zn = log(dot(z, z)) / 2.0; | |
| float nu = log(log_zn / 0.69314718) / 0.69314718; | |
| n = n + 1.0 - nu; | |
| } else { | |
| n = max_i; | |
| } | |
| float t = n / max_i; | |
| vec3 color = vec3(0.0); | |
| if (n < max_i) { | |
| // Vibrant electric color scheme | |
| color = palette(t * 2.0 + 0.1, | |
| vec3(0.4, 0.4, 0.4), | |
| vec3(0.5, 0.5, 0.6), | |
| vec3(1.0, 1.0, 1.0), | |
| vec3(0.0, 0.33, 0.67) | |
| ); | |
| } | |
| gl_FragColor = vec4(color, 1.0); | |
| } | |
| </script> | |
| <script> | |
| const canvas = document.getElementById('glcanvas'); | |
| const gl = canvas.getContext('webgl'); | |
| const cValueDisplay = document.getElementById('c-value'); | |
| if (!gl) { | |
| alert('WebGL not supported by your browser.'); | |
| } | |
| function createShader(gl, type, source) { | |
| const shader = gl.createShader(type); | |
| gl.shaderSource(shader, source); | |
| gl.compileShader(shader); | |
| if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { | |
| console.error(gl.getShaderInfoLog(shader)); | |
| gl.deleteShader(shader); | |
| return null; | |
| } | |
| return shader; | |
| } | |
| const vsSource = document.getElementById('vs').text; | |
| const fsSource = document.getElementById('fs').text; | |
| const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource); | |
| const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource); | |
| const program = gl.createProgram(); | |
| gl.attachShader(program, vertexShader); | |
| gl.attachShader(program, fragmentShader); | |
| gl.linkProgram(program); | |
| if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
| console.error('Unable to initialize the shader program:', gl.getProgramInfoLog(program)); | |
| } | |
| gl.useProgram(program); | |
| const resolutionLocation = gl.getUniformLocation(program, "u_resolution"); | |
| const cLocation = gl.getUniformLocation(program, "u_c"); | |
| const positionBuffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
| gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ | |
| -1, -1, | |
| 1, -1, | |
| -1, 1, | |
| -1, 1, | |
| 1, -1, | |
| 1, 1, | |
| ]), gl.STATIC_DRAW); | |
| const positionAttributeLocation = gl.getAttribLocation(program, "position"); | |
| gl.enableVertexAttribArray(positionAttributeLocation); | |
| gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0); | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| } | |
| window.addEventListener('resize', resizeCanvas); | |
| resizeCanvas(); | |
| let startTime = performance.now(); | |
| function render() { | |
| let currentTime = performance.now(); | |
| let time = (currentTime - startTime) * 0.00002; | |
| // Smoothly orbit near interesting features of the Mandelbrot boundary | |
| let r = 1.0 + 0.1 * Math.cos(time * 4.7311); | |
| let a = Math.sin(time) + 0.25 * (1.0 + Math.cos(2.0 * time)) - 0.2; | |
| let cx = r * 0.75 * a*a*a; | |
| let cy = r * 0.75 * Math.cos(time); | |
| const sign = cy >= 0 ? '+' : '-'; | |
| cValueDisplay.innerHTML = `C = ${cx.toFixed(5)} ${sign} ${Math.abs(cy).toFixed(5)}i`; | |
| gl.uniform2f(resolutionLocation, canvas.width, canvas.height); | |
| gl.uniform2f(cLocation, cx, cy); | |
| gl.drawArrays(gl.TRIANGLES, 0, 6); | |
| requestAnimationFrame(render); | |
| } | |
| requestAnimationFrame(render); | |
| </script> | |
| </body> | |
| </html> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Animates Julia sets for the polynomial Z' = Z^2 + C by having the point C orbit near the border of the Mandelbrot set.