Creative Code Lab
Generate interactive generative art and creative coding sketches. Inspired by Leisure Lab (43 sketches by KainXu).
Overview
Creative coding = algorithms made visible. Every sketch transforms math into something you can see, touch, and feel.
The output format is flexible: single self-contained HTML file, React component, Next.js page, p5.js sketch, or raw Canvas/WebGL - whatever fits the user's project.
When to Use
- User wants generative/procedural art
- Building animated backgrounds or hero sections
- Creating interactive visual experiments
- Implementing particle systems, flow fields, shaders, fractals
- "Make something cool/beautiful with code"
- Image-to-art transformations
- Creative 404 pages, loading screens, visual flourishes
Technique Catalog
Pick the right approach for the visual:
What visual are you building?
|
+-- Flowing, organic motion?
| +-- Particles following forces --> Flow Field (Perlin noise)
| +-- Smooth color blending --> Fluid Shader (WebGL fragment)
| +-- Swirling paint effect --> Fluid Swirl (iterative sine + spiral distortion)
|
+-- Nature / biology?
| +-- Trees, plants, corals --> L-System (recursive branching)
| +-- Cell division, growth --> Cellular Automata (neighbor rules)
| +-- Veins, rivers, cracks --> Diffusion-Limited Aggregation
|
+-- Geometric / mathematical?
| +-- Mondrian, grids --> Recursive Subdivision
| +-- Space-filling patterns --> Hilbert/Peano Curves
| +-- Spirals, roses --> Polar Coordinate Math
| +-- Vortex patterns --> Parametric equations in (r, theta)
|
+-- Text effects?
| +-- Text from particles --> Canvas pixel sampling + particle system
| +-- Neon glow --> CSS text-shadow stacking or shader
| +-- Glitch effect --> Random clip-path + color channel offset
| +-- Blur/reveal --> CSS filter animation or shader blur
|
+-- Interactive / mouse-driven?
| +-- Particles scatter from cursor --> Distance-based force repulsion
| +-- Drawing / painting --> Canvas stroke with velocity-based width
| +-- Cursor trail effects --> Ring buffer of positions + fade
|
+-- 3D scenes?
| +-- Product showcase --> Three.js + GLTF + orbit controls
| +-- Abstract geometry --> Three.js + custom shaders
| +-- Camera depth blur --> Three.js postprocessing (DOF)
|
+-- Image transformation?
+-- Photo to painting --> Algorithmic brush strokes on canvas
+-- Pointillism / dots --> Pixel sampling + circle rendering
+-- Pixel decomposition --> Grid sampling + animated scatter
Core Algorithms Reference
1. Perlin Noise Flow Field
The foundation of organic-looking particle motion. Particles follow a vector field generated by noise.
// Core concept: noise(x, y) returns smooth random value 0-1
// Map to angle: angle = noise(x * scale, y * scale) * TWO_PI
// Each particle follows the angle at its grid position
class FlowField {
constructor(canvas, particleCount = 2000) {
this.ctx = canvas.getContext('2d');
this.w = canvas.width;
this.h = canvas.height;
this.scale = 0.005; // Noise zoom (smaller = smoother)
this.speed = 2;
this.particles = Array.from({ length: particleCount }, () => ({
x: Math.random() * this.w,
y: Math.random() * this.h,
prevX: 0, prevY: 0
}));
}
// Attempt at simplex-like noise (for self-contained sketches)
// For production, use a proper noise library
noise2D(x, y) {
const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
return n - Math.floor(n);
}
update(time) {
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.02)'; // Trail fade
this.ctx.fillRect(0, 0, this.w, this.h);
for (const p of this.particles) {
p.prevX = p.x;
p.prevY = p.y;
const angle = this.noise2D(
p.x * this.scale + time * 0.0001,
p.y * this.scale
) * Math.PI * 4;
p.x += Math.cos(angle) * this.speed;
p.y += Math.sin(angle) * this.speed;
// Wrap around edges
if (p.x < 0) p.x = this.w;
if (p.x > this.w) p.x = 0;
if (p.y < 0) p.y = this.h;
if (p.y > this.h) p.y = 0;
this.ctx.strokeStyle = `hsla(${angle * 30}, 70%, 60%, 0.3)`;
this.ctx.beginPath();
this.ctx.moveTo(p.prevX, p.prevY);
this.ctx.lineTo(p.x, p.y);
this.ctx.stroke();
}
}
}
Key parameters to tune:
scale(0.001-0.01): Noise zoom. Smaller = wider, smoother curvesspeed(1-5): How fast particles move- Trail fade alpha (0.01-0.1): Lower = longer trails
particleCount: More = denser field, watch performance- Color mapping: Map angle, position, or velocity to hue
2. L-System (Procedural Trees/Plants)
Recursive string rewriting that draws botanical structures.
function lSystem(axiom, rules, iterations) {
let current = axiom;
for (let i = 0; i < iterations; i++) {
current = current.split('').map(c => rules[c] || c).join('');
}
return current;
}
function drawLSystem(ctx, instructions, len, angle) {
const stack = [];
for (const char of instructions) {
switch (char) {
case 'F': // Draw forward
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(0, -len);
ctx.stroke();
ctx.translate(0, -len);
break;
case '+': ctx.rotate(angle); break; // Turn right
case '-': ctx.rotate(-angle); break; // Turn left
case '[': // Save state (branch start)
stack.push(ctx.getTransform());
break;
case ']': // Restore state (branch end)
ctx.setTransform(stack.pop());
break;
}
}
}
// Classic tree
const tree = lSystem('F', { 'F': 'FF+[+F-F-F]-[-F+F+F]' }, 4);
// Fern
const fern = lSystem('X', { 'X': 'F+[[X]-X]-F[-FX]+X', 'F': 'FF' }, 6);
Common L-System presets:
| Name | Axiom | Rules | Angle |
|---|---|---|---|
| Binary tree | F | F -> FF+[+F-F]-[-F+F] | 25deg |
| Fern | X | X -> F+[[X]-X]-F[-FX]+X, F -> FF | 25deg |
| Bush | F | F -> F[+FF][-FF]F[-F][+F]F | 20deg |
| Seaweed | F | F -> FF-[-F+F+F]+[+F-F-F] | 22deg |
3. WebGL Fragment Shader (Fluid/Gradient Effects)
For GPU-accelerated visuals. Single HTML file with inline shader.
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl');
canvas.width = innerWidth;
canvas.height = innerHeight;
const vertSrc = `attribute vec2 p; void main(){gl_Position=vec4(p,0,1);}`;
const fragSrc = `
precision mediump float;
uniform float t;
uniform vec2 r; // resolution
uniform vec2 m; // mouse
void main() {
vec2 uv = gl_FragCoord.xy / r;
vec2 mouse = m / r;
// Fluid swirl: iterative sine distortion
for (int i = 0; i < 8; i++) {
uv = vec2(
sin(uv.y * 4.0 + t + float(i)) * 0.4 + uv.x,
cos(uv.x * 4.0 + t + float(i)) * 0.4 + uv.y
);
}
// Distance from mouse adds interaction
float d = length(uv - mouse) * 2.0;
vec3 col = vec3(
sin(uv.x * 3.0 + t) * 0.5 + 0.5,
sin(uv.y * 3.0 + t * 1.3) * 0.5 + 0.5,
sin((uv.x + uv.y) * 2.0 + t * 0.7) * 0.5 + 0.5
);
gl_FragColor = vec4(col * (1.0 - d * 0.3), 1.0);
}`;
// Boilerplate: compile, link, draw fullscreen quad
function compile(type, src) {
const s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
return s;
}
const prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vertSrc));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, fragSrc));
gl.linkProgram(prog);
gl.useProgram(prog);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1,1,-1,-1,1,1,1]), gl.STATIC_DRAW);
const p = gl.getAttribLocation(prog, 'p');
gl.enableVertexAttribArray(p);
gl.vertexAttribPointer(p, 2, gl.FLOAT, false, 0, 0);
const tLoc = gl.getUniformLocation(prog, 't');
const rLoc = gl.getUniformLocation(prog, 'r');
const mLoc = gl.getUniformLocation(prog, 'm');
let mx = 0, my =