Custom Code Experiment •
Barnsley Fern
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, maximum-scale=1.0, user-scalable=no">
<title>Barnsley Fern Fractal</title>
<style>
html, body {
background: black;
overflow: hidden;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
}
#world {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
max-width: 95vw;
max-height: 70vh;
width: auto;
height: auto;
}
.info {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.85);
color: #ffffff;
padding: 20px;
border-radius: 12px;
max-width: 320px;
font-size: 14px;
line-height: 1.6;
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
}
.info .title {
font-size: 20px;
color: #4ade80;
text-align: center;
margin: 0 0 15px 0;
font-weight: bold;
}
.info p {
margin: 10px 0;
color: #e5e7eb;
}
.info a {
color: #60a5fa;
text-decoration: none;
transition: color 0.3s;
}
.info a:hover {
color: #93c5fd;
text-decoration: underline;
}
.counter {
position: absolute;
top: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.85);
color: #ffffff;
padding: 15px 20px;
border-radius: 12px;
font-size: 16px;
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
font-weight: 500;
}
.instructions {
position: absolute;
bottom: 30px;
left: 0;
width: 100%;
text-align: center;
color: #ffffff;
font-size: 14px;
z-index: 10;
padding: 15px;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.instructions strong {
color: #4ade80;
font-weight: 600;
}
/* Mobile-specific styles */
@media (max-width: 768px) {
#world {
max-width: 90vw;
max-height: 60vh;
}
.info {
position: relative;
top: auto;
right: auto;
left: 50%;
transform: translateX(-50%);
max-width: 85vw;
margin: 20px auto;
font-size: 13px;
padding: 15px;
}
.counter {
top: 10px;
left: 50%;
transform: translateX(-50%);
width: auto;
text-align: center;
padding: 12px 18px;
font-size: 14px;
}
.instructions {
font-size: 13px;
padding: 12px;
bottom: 20px;
}
.info .title {
font-size: 18px;
}
}
@media (max-width: 480px) {
#world {
max-width: 85vw;
max-height: 55vh;
}
.info {
max-width: 90vw;
padding: 12px;
font-size: 12px;
}
.counter {
padding: 10px 15px;
font-size: 13px;
top: 10px;
}
.instructions {
font-size: 12px;
padding: 10px;
bottom: 15px;
}
.info .title {
font-size: 16px;
margin-bottom: 10px;
}
}
/* Landscape orientation optimization */
@media (max-height: 500px) and (orientation: landscape) {
#world {
max-height: 80vh;
max-width: 70vw;
}
.info {
max-width: 280px;
font-size: 12px;
padding: 12px;
}
.counter {
padding: 8px 12px;
font-size: 13px;
}
.instructions {
padding: 8px;
font-size: 12px;
}
}
/* Prevent text selection on mobile */
.info, .counter, .instructions {
user-select: none;
-webkit-user-select: none;
}
</style>
</head>
<body>
<canvas id="world"></canvas>
<div class="counter">
Points drawn: <span id="counter">0</span>
</div>
<div class="info">
<p>This fractal is generated using an iterated function system (IFS). Each point is transformed by one of four affine transformations chosen randomly with different probabilities.</p>
<p><strong>Tap anywhere to reset the animation.</strong> The fern will continue generating points until it reaches maximum detail.</p>
<p><strong>References:</strong><br>
<a href="http://en.wikipedia.org/wiki/Iterated_function_system" target="_blank">Iterated Function System</a><br>
<a href="http://en.wikipedia.org/wiki/Barnsley_fern" target="_blank">Barnsley Fern</a><br>
<a href="http://mathworld.wolfram.com/BarnsleysFern.html" target="_blank">MathWorld: Barnsley's Fern</a></p>
</div>
<div class="instructions">
<strong>Tap anywhere to reset</strong>
</div>
<script>
// Canvas dimensions
const W = 350;
const H = 500;
// Fixed speed
const speed = 2;
// Transformation functions for the Barnsley fern
const transformations = [
[
(x, y) => 0,
(x, y) => 0.16 * y,
1
],
[
(x, y) => 0.2 * x - 0.26 * y,
(x, y) => 0.23 * x + 0.22 * y + 1.6,
7
],
[
(x, y) => -0.15 * x + 0.28 * y,
(x, y) => 0.26 * x + 0.24 * y + 0.44,
7
],
[
(x, y) => 0.85 * x + 0.04 * y,
(x, y) => -0.04 * x + 0.85 * y + 1.6,
85
]
];
const maxDraws = 200000;
const xRange = [ -2.182, 2.6558 ];
const yRange = [ 0, 9.9983 ];
const canvas = document.getElementById("world");
const ctx = canvas.getContext("2d");
// Handle high DPI displays
const dpr = window.devicePixelRatio || 1;
canvas.width = W * dpr;
canvas.height = H * dpr;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.scale(dpr, dpr);
const colorStops = [
[ 0, "rgba(255, 255, 255, 1)" ],
[ 1000, "rgba(253, 253, 52, 0.6)" ],
[ 5000, "rgba(0, 171, 56, 0.7)" ],
[ 10000, "rgba(168, 210, 110, 0.7)" ],
[ 20000, "rgba(246, 243, 27, 0.6)" ],
[ 40000, "rgba(138, 215, 39, 0.7)" ],
[ 65000, "rgba(243, 205, 8, 0.5)" ],
[ 90000, "rgba(150, 204, 104, 0.4)" ],
[ 125000, "rgba(137, 230, 101, 0.5)" ],
[ 150000, "rgba(246, 243, 27, 0.5)" ],
[ 190000, "rgba(255, 255, 255, 0.8)" ]
];
function animationCurve(frame) {
if (frame < 1000) return 25;
if (frame < 5000) return 50;
if (frame < 10000) return 75;
if (frame < 20000) return 100;
if (frame < 40000) return 150;
if (frame < 65000) return 250;
if (frame < 90000) return 400;
if (frame < 150000) return 600;
if (frame < 190000) return 400;
if (frame < 200000) return 100;
return 100;
}
// Build lookup table for transformations
let lut = {};
let pMax = 0;
for (let i = 0; i < transformations.length; i++) {
const row = transformations[i];
for (let j = pMax; j < row[2] + pMax; j++) {
lut[j] = [row[0], row[1]];
}
pMax += row[2];
}
let numDraws = 0;
let colors = [];
let animationId = null;
let isAnimating = false;
function getColors() {
colors = colorStops.slice();
return function() {
if (colors[0] && numDraws >= colors[0][0]) {
const colorStop = colors.shift();
ctx.fillStyle = colorStop[1];
}
};
}
// Initialize color
let updateColor = getColors();
const xSpan = xRange[1] - xRange[0];
const ySpan = yRange[1] - yRange[0];
// Update counter display
function updateCounter() {
document.getElementById("counter").textContent = numDraws.toLocaleString();
}
// Random number generator
function rand(a, b) {
return (b - a) * Math.random() + a;
}
// Main render function
function render() {
if (!isAnimating) return;
if (numDraws > maxDraws) {
isAnimating = false;
return;
}
updateColor();
const drawsPerFrame = speed * animationCurve(numDraws);
for (let i = 0; i < drawsPerFrame && numDraws < maxDraws; i++) {
let iter = 0;
let x = rand(xRange[0], xRange[1]);
let y = rand(yRange[0], yRange[1]);
// Apply multiple iterations to reach attractor
while (iter++ < 50) {
const f = lut[Math.floor(rand(0, pMax))];
const newX = f[0](x, y);
const newY = f[1](x, y);
x = newX;
y = newY;
}
// Map to canvas coordinates
const canvasX = W * (x - xRange[0]) / xSpan;
const canvasY = H * (1 - y / ySpan);
ctx.fillRect(canvasX, canvasY, 1, 1);
numDraws++;
}
updateCounter();
// Continue animation
if (isAnimating) {
animationId = requestAnimationFrame(render);
}
}
// Start animation
function startAnimation() {
if (!isAnimating) {
isAnimating = true;
animationId = requestAnimationFrame(render);
}
}
// Stop animation
function stopAnimation() {
isAnimating = false;
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}
// Reset function - FIXED VERSION
function resetFern() {
// Stop current animation
stopAnimation();
// Reset counters and colors
numDraws = 0;
updateColor = getColors();
// Clear canvas
ctx.clearRect(0, 0, W, H);
// Reset to initial color
ctx.fillStyle = colorStops[0][1];
// Update counter
updateCounter();
// Restart animation
setTimeout(() => {
startAnimation();
}, 50);
}
// Set up event handlers
canvas.addEventListener('click', resetFern);
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
resetFern();
});
// Initialize and start animation
updateCounter();
startAnimation();
// Handle window resize
function handleResize() {
canvas.style.maxWidth = '95vw';
canvas.style.maxHeight = '70vh';
}
window.addEventListener('resize', handleResize);
handleResize(); // Initial call
// Clean up on page unload
window.addEventListener('beforeunload', () => {
stopAnimation();
});
</script>
</body>
</html>