Custom Code Experiment •
Julia Set
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Julia Set</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background: #000;
color: #eee;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
overflow: hidden;
height: 100vh;
}
.viewer-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
#fractal {
display: block;
width: 100%;
height: 100%;
}
.control-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(20, 20, 30, 0.85);
backdrop-filter: blur(10px);
border-radius: 12px;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
min-width: 200px;
z-index: 50;
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.control-row {
display: flex;
align-items: center;
gap: 0.5rem;
justify-content: space-between;
}
.control-label {
font-size: 0.9rem;
min-width: 100px;
text-align: left;
}
.slider-container {
display: flex;
align-items: center;
gap: 0.8rem;
flex: 1;
}
input[type="range"] {
flex: 1;
height: 6px;
border-radius: 3px;
background: #333;
outline: none;
-webkit-appearance: none;
margin: 0;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #6a11cb;
cursor: pointer;
}
.iteration-value {
font-family: monospace;
font-size: 0.9rem;
min-width: 35px;
text-align: center;
background: rgba(255, 255, 255, 0.1);
padding: 0.2rem 0.4rem;
border-radius: 4px;
}
select {
background: #1a1a2e;
color: white;
border: 1px solid #333;
border-radius: 6px;
padding: 0.4rem;
cursor: pointer;
flex: 1;
}
@media (max-width: 768px) {
.control-panel {
top: 10px;
right: 10px;
left: 10px;
flex-direction: column;
min-width: auto;
padding: 0.8rem;
}
.control-group {
width: 100%;
}
.control-row {
width: 100%;
gap: 0.8rem;
}
.control-label {
min-width: 90px;
font-size: 0.85rem;
}
.slider-container {
gap: 0.6rem;
}
input[type="range"], select {
flex: 1;
}
.iteration-value {
font-size: 0.85rem;
min-width: 30px;
}
}
@media (max-width: 480px) {
.control-panel {
padding: 0.7rem;
gap: 0.8rem;
}
.control-label {
min-width: 80px;
font-size: 0.8rem;
}
.iteration-value {
font-size: 0.8rem;
min-width: 28px;
padding: 0.15rem 0.3rem;
}
}
</style>
</head>
<body>
<div class="viewer-container">
<canvas id="fractal"></canvas>
<div class="control-panel">
<div class="control-group">
<div class="control-row">
<div class="control-label">Iterations:</div>
<div class="slider-container">
<input type="range" id="iterSlider" min="10" max="100" value="10">
<span class="iteration-value" id="iterationValue">10</span>
</div>
</div>
</div>
<div class="control-group">
<div class="control-row">
<div class="control-label">Colour Palette:</div>
<select id="paletteSelect"></select>
</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('fractal');
const ctx = canvas.getContext('2d');
const state = {
center_x: 0,
center_y: 0,
scale: 1.0,
max_iter: 10,
palette: 0,
c_re: -0.7,
c_im: 0.27015,
frame: 0,
width: 0,
height: 0,
animationId: null
};
const palettes = [
t => {
const r = Math.sin(15 * t + 0) * 127 + 128;
const g = Math.sin(15 * t + 2) * 127 + 128;
const b = Math.sin(15 * t + 4) * 127 + 128;
return [r, g, b];
},
t => {
const r = 255;
const g = Math.floor(255 * t);
const b = Math.floor(100 * t * t);
return [r, g, b];
},
t => {
const r = Math.floor(20 + 50 * t);
const g = Math.floor(100 + 100 * t);
const b = Math.floor(200 + 55 * t);
return [r, g, b];
},
t => {
const r = Math.floor(50 * t);
const g = Math.floor(150 + 50 * t);
const b = Math.floor(50 * t);
return [r, g, b];
},
t => {
const r = Math.floor(150 + 50 * Math.sin(10 * t));
const g = Math.floor(50 * t);
const b = Math.floor(150 + 50 * Math.cos(10 * t));
return [r, g, b];
},
t => {
const r = Math.floor(255 - 50 * t);
const g = Math.floor(150 - 100 * t);
const b = Math.floor(100 + 50 * t);
return [r, g, b];
}
];
const paletteNames = [
"Rainbow", "Fire", "Ocean", "Forest", "Purple", "Sunset"
];
function initUI() {
const paletteSelect = document.getElementById('paletteSelect');
paletteNames.forEach((name, i) => {
const option = document.createElement('option');
option.value = i;
option.textContent = name;
paletteSelect.appendChild(option);
});
const iterSlider = document.getElementById('iterSlider');
const iterationValue = document.getElementById('iterationValue');
iterSlider.addEventListener('input', (e) => {
state.max_iter = parseInt(e.target.value);
iterationValue.textContent = state.max_iter;
renderFractal();
});
document.getElementById('paletteSelect').addEventListener('change', (e) => {
state.palette = parseInt(e.target.value);
renderFractal();
});
}
function animate() {
state.frame++;
const t = state.frame * 0.01;
state.c_re = 0.7885 * Math.cos(t);
state.c_im = 0.7885 * Math.sin(t);
renderFractal();
state.animationId = requestAnimationFrame(animate);
}
function resizeCanvas() {
state.width = canvas.width = window.innerWidth;
state.height = canvas.height = window.innerHeight;
renderFractal();
}
function renderFractal() {
const imageData = ctx.createImageData(state.width, state.height);
const data = imageData.data;
const aspect = state.width / state.height;
const view_w = 3.5 / state.scale;
const view_h = view_w / aspect;
const left = state.center_x - view_w / 2;
const top = state.center_y - view_h / 2;
for (let y = 0; y < state.height; y++) {
for (let x = 0; x < state.width; x++) {
const cx = left + x * view_w / state.width;
const cy = top + y * view_h / state.height;
let zx = cx;
let zy = cy;
const cx0 = state.c_re;
const cy0 = state.c_im;
let i = 0;
while (zx * zx + zy * zy <= 4 && i < state.max_iter) {
const tmp = zx * zx - zy * zy + cx0;
zy = 2 * zx * zy + cy0;
zx = tmp;
i++;
}
const idx = (y * state.width + x) * 4;
if (i === state.max_iter) {
data[idx] = 0;
data[idx+1] = 0;
data[idx+2] = 0;
data[idx+3] = 255;
} else {
const t = i / state.max_iter;
const [r, g, b] = palettes[state.palette](t);
data[idx] = r;
data[idx+1] = g;
data[idx+2] = b;
data[idx+3] = 255;
}
}
}
ctx.putImageData(imageData, 0, 0);
}
window.addEventListener('load', () => {
resizeCanvas();
initUI();
renderFractal();
animate();
});
window.addEventListener('resize', resizeCanvas);
</script>
</body>
</html>