Skip to content

Instantly share code, notes, and snippets.

@phkahler
Created August 9, 2026 18:57
Show Gist options
  • Select an option

  • Save phkahler/09dca2579cc5397e9fccef7e48d857b0 to your computer and use it in GitHub Desktop.

Select an option

Save phkahler/09dca2579cc5397e9fccef7e48d857b0 to your computer and use it in GitHub Desktop.
<!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>
@phkahler

phkahler commented Aug 9, 2026

Copy link
Copy Markdown
Author

Animates Julia sets for the polynomial Z' = Z^2 + C by having the point C orbit near the border of the Mandelbrot set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment