🤖 AI
Here’s a complete spirograph sketch with a glowing, color‑shifting line that slowly builds up the pattern over time.
I’m using a separate `p5.Graphics` layer to keep the path persistent while redrawing the circles and pen each frame. The math is a classic **hypotrochoid** (small circle rolling inside a big circle).
---
### `index.html`
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>p5.js Spirograph</title>
<link rel="stylesheet" href="style.css">
<!-- Load p5.js (must come before sketch.js) -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>
```
---
### `style.css`
```css
body {
margin: 0;
padding: 0;
overflow: hidden;
background: #000; /* fallback dark background behind canvas */
}
canvas {
display: block;
}
```
---
### `sketch.js`
```js
// Animated spirograph using a hypotrochoid (inner circle rolling inside outer circle)
// Dark background, glowing, color-shifting line that slowly builds up over time.
let R; // Radius of the fixed outer circle
let r; // Radius of the rolling inner circle
let d; // Distance from inner circle center to pen (offset)
let k; // (R - r) / r ratio
let t = 0; // Parameter (angle) for animation
let prevX, prevY; // Previous pen position
let centerX, centerY;
let hueVal = 0; // For colorful gradient along the path
let pathLayer; // Off-screen graphics buffer to keep the drawn path
function setup() {
createCanvas(windowWidth, windowHeight);
colorMode(HSB, 360, 100, 100, 100); // HSB with alpha 0–100
smooth();
// Layer that accumulates the drawing over time
pathLayer = createGraphics(windowWidth, windowHeight);
pathLayer.colorMode(HSB, 360, 100, 100, 100);
pathLayer.background(0, 0, 0); // black
pathLayer.smooth();
pathLayer.strokeCap(ROUND);
initSpiroParams();
}
// Initialize / recalc spirograph parameters based on canvas size
function initSpiroParams() {
centerX = width / 2;
centerY = height / 2;
const minDim = min(width, height);
// Tuned values for interesting, intricate patterns:
R = minDim * 0.35; // big circle radius
r = R * 0.38; // small circle radius
d = r * 1.25; // pen offset from center of inner circle
k = (R - r) / r; // angle ratio
// Start param
t = 0;
// Initial pen position at t = 0
const x0 = centerX + (R - r) + d; // cos(0) = 1
const y0 = centerY; // sin(0) = 0
prevX = x0;
prevY = y0;
hueVal = 0;
}
function draw() {
// Clear the main canvas each frame, but NOT the pathLayer
background(0, 0, 0); // black (HSB: hue=0, sat=0, bright=0)
// Draw multiple small steps per frame for a smoother curve
const stepsPerFrame = 4;
for (let i = 0; i < stepsPerFrame; i++) {
t += 0.01; // Smaller = slower drawing, more detailed animation
// Hypotrochoid parametric equations (inner circle rolling inside outer)
// x(θ) = (R - r) cos θ + d cos(((R - r)/r) θ)
// y(θ) = (R - r) sin θ - d sin(((R - r)/r) θ)
const cosT = cos(t);
const sinT = sin(t);
const angle2 = k * t;
const cos2 = cos(angle2);
const sin2 = sin(angle2);
const x = centerX + (R - r) * cosT + d * cos2;
const y = centerY + (R - r) * sinT - d * sin2;
// Advance hue for a smooth gradient as we draw
hueVal = (hueVal + 0.4) % 360;
// --- Glowing line on the path layer ---
// Outer "glow" stroke (thicker, lower alpha)
pathLayer.stroke(hueVal, 80, 60, 35); // hue, sat, bright, alpha (0–100)
pathLayer.strokeWeight(8);
pathLayer.line(prevX, prevY, x, y);
// Inner bright core stroke (thinner, higher alpha)
pathLayer.stroke(hueVal, 95, 100, 100);
pathLayer.strokeWeight(3);
pathLayer.line(prevX, prevY, x, y);
prevX = x;
prevY = y;
}
// Draw the accumulated path
image(pathLayer, 0, 0);
// --- Visualize the circles and pen on top (ephemeral) ---
// Outer fixed circle
noFill();
stroke(210, 20, 40, 30); // faint bluish outline
strokeWeight(1.5);
circle(centerX, centerY, 2 * R);
// Position of inner rolling circle center
const innerCX = centerX + (R - r) * cos(t);
const innerCY = centerY + (R - r) * sin(t);
// Inner rolling circle
stroke(210, 20, 60, 40);
strokeWeight(1.5);
circle(innerCX, innerCY, 2 * r);
// Pen (current drawing point)
noStroke();
// Soft halo for glow
fill(hueVal, 80, 100, 40);
circle(prevX, prevY, 18);
// Solid core of the pen
fill(hueVal, 95, 100, 100);
circle(prevX, prevY, 8);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Rebuild the path layer to match new size (pattern restarts)
pathLayer = createGraphics(windowWidth, windowHeight);
pathLayer.colorMode(HSB, 360, 100, 100, 100);
pathLayer.background(0, 0, 0);
pathLayer.smooth();
pathLayer.strokeCap(ROUND);
initSpiroParams();
}
```
---
**Key techniques (for reference):**
- The persistent drawing layer uses [`createGraphics`](https://p5js.org/reference/#/p5/createGraphics), so the spirograph path builds up over time while the main canvas is redrawn each frame.
- The hypotrochoid curve is based on classic spirograph math; two circles rotating at different speeds are encoded in the relation between `R`, `r`, and `d`.
- Color gradient is achieved by gradually changing `hueVal` in HSB color space ([`colorMode`](https://p5js.org/reference/#/p5/colorMode)).
- The glow effect comes from drawing two strokes for each segment: a thick, low-alpha stroke and a thin, bright core stroke.
You can tweak `R`, `r`, `d`, `stepsPerFrame`, and the increment of `t` to explore different intricate patterns and drawing speeds.