for web wensday on corbun does weird webs

This interactive sketch creates a glowing kaleidoscope that mirrors your brush strokes symmetrically around the canvas center. As you drag the mouse, colorful lines radiate outward in a configurable number of symmetrical slices, with color cycling over time and trails that fade slowly for a mesmerizing painted effect.

🧪 Try This!

Experiment with the code by making these changes:

  1. Change the number of slices without arrow keys — Edit the starting symmetry value directly to see more or fewer mirror copies instantly when you reload the sketch
  2. Make strokes fade way faster — Increase the alpha value in the trailing background so old strokes disappear quickly instead of glowing for a long time
  3. Make the center lines super thick — Increase the maximum stroke weight from 12 to 30 so strokes near the center become chunky glowing lines
  4. Cycle through colors way faster — Increase the frameCount multiplier from 0.5 to 3 so hues shift rapidly through the rainbow spectrum
  5. Remove the mirroring effect — Delete the scale(1, -1) block so strokes only rotate without flipping—creating a radial sunburst instead of symmetrical mirror patterns
  6. Start with a colorful background — Change the starting background from solid black to a dim color, then watch strokes trail over a colored canvas
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch turns your mouse into a kaleidoscope painter. Drag anywhere on the canvas and watch your strokes multiply and mirror symmetrically around the center, with colors that cycle through the spectrum and fade into glowing trails. The magic comes from three p5.js techniques: transformations (translate and rotate) to mirror strokes around a central point, HSB color mode to cycle hues smoothly, and a semi-transparent background that creates motion blur instead of clearing completely each frame.

The code is organized into setup() which configures the canvas and color mode, draw() which handles the main animation and symmetry logic, keyPressed() to let you change the number of slices or clear the canvas, and windowResized() to handle screen size changes. By studying it, you will learn how to use the transformation stack (push/pop) to create complex geometric patterns, how to combine frameCount and distance measurements for dynamic color, and how semi-transparent backgrounds can turn a static canvas into a trail-painting surface.

⚙️ How It Works

  1. When the sketch loads, setup() creates a canvas that fills the browser window and switches to HSB color mode so hues can cycle smoothly from 0-360 degrees.
  2. Every frame, draw() creates a semi-transparent black background that doesn't fully erase previous strokes—instead, it fades them slightly, creating a glowing trail effect.
  3. When you press and drag the mouse, the code calculates the distance from the canvas center and uses it to control both the stroke weight (farther = thinner) and the hue (farther and newer = different colors).
  4. A for-loop rotates and redraws your brush stroke multiple times around the center point, with each iteration using rotate() to spin the coordinate system and scale() to flip it vertically, creating mirror symmetry.
  5. Arrow keys let you change the symmetry variable to create more or fewer slices, and pressing C clears the canvas by drawing a fully opaque background.

🎓 Concepts You'll Learn

Transformations (translate, rotate, scale)Symmetry and mirroringHSB color modeSemi-transparent backgrounds for trailsDistance mappingCoordinate system manipulationKeyboard interaction

📝 Code Breakdown

setup()

setup() runs once when the sketch starts. Use it to initialize your canvas size, color modes, and any starting values. Here it prepares HSB mode so the draw() loop can cycle through hues smoothly.

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Use HSB so we can easily cycle through hues
  colorMode(HSB, 360, 100, 100, 100);
  background(0, 0, 0, 100); // start with solid black
}
Line-by-line explanation (3 lines)

🔧 Subcomponents:

function-call Canvas Setup createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window

function-call HSB Color Configuration colorMode(HSB, 360, 100, 100, 100);

Switches from RGB to HSB (Hue, Saturation, Brightness) so colors cycle smoothly through the spectrum

createCanvas(windowWidth, windowHeight);
Creates a canvas that matches your browser window size so the kaleidoscope fills the entire screen
colorMode(HSB, 360, 100, 100, 100);
Switches to HSB color mode where hue ranges 0-360 (red→orange→yellow→...→red), and saturation/brightness each range 0-100. This makes it easy to cycle through all rainbow colors by incrementing a single number.
background(0, 0, 0, 100);
Draws a fully opaque black background to start with a clean canvas (the 100 is full alpha, no transparency)

draw()

draw() runs 60 times per second. Every frame, it updates the background (with transparency to leave trails), checks if the mouse is pressed, and if so, draws the current brush stroke rotated and mirrored multiple times around the center. The combination of rotate() and scale() creates the kaleidoscope effect—each stroke is drawn 'symmetry' times at different angles, creating a perfect mirror pattern.

🔬 This loop draws the stroke 'symmetry' times with rotation and mirroring. What happens if you remove the scale(1, -1) block (the push/scale/line/pop)? You'll lose the mirror—try it and see the pattern change from symmetrical to radial-only.

    for (let i = 0; i < symmetry; i++) {
      rotate(angleStep);

      // Original stroke
      line(mx, my, pmx, pmy);

      // Mirrored stroke (flip vertically)
      push();
      scale(1, -1);
      line(mx, my, pmx, pmy);
      pop();
    }

🔬 The middle line sets stroke weight to 'sw', which changes based on distance. What if you changed it to a constant like strokeWeight(5)? All strokes would be the same thickness no matter where you draw—try it and see if the patterns look flatter or more uniform.

    stroke(hue, 80, 100, 100);
    strokeWeight(sw);
    noFill();
function draw() {
  // Slightly transparent background for trailing effect
  background(0, 0, 0, 8);

  translate(width / 2, height / 2); // center the coordinate system

  if (mouseIsPressed) {
    // Mouse position relative to center
    const mx = mouseX - width / 2;
    const my = mouseY - height / 2;
    const pmx = pmouseX - width / 2;
    const pmy = pmouseY - height / 2;

    // Distance from center controls stroke weight
    const d = dist(0, 0, mx, my);
    const sw = map(d, 0, min(width, height) / 2, 12, 1);

    // Hue cycles over time and with distance
    const hue = (frameCount * 0.5 + d * 0.2) % 360;

    stroke(hue, 80, 100, 100);
    strokeWeight(sw);
    noFill();

    const angleStep = TWO_PI / symmetry;

    // Draw the stroke rotated and mirrored around the center
    for (let i = 0; i < symmetry; i++) {
      rotate(angleStep);

      // Original stroke
      line(mx, my, pmx, pmy);

      // Mirrored stroke (flip vertically)
      push();
      scale(1, -1);
      line(mx, my, pmx, pmy);
      pop();
    }
  }

  // Reset transform to draw UI text in the top-left corner
  resetMatrix();

  // Small translucent panel for instructions
  noStroke();
  fill(0, 0, 0, 40);
  rect(10, 10, 260, 60, 8);

  fill(0, 0, 100, 90); // white in HSB
  textSize(12);
  textAlign(LEFT, TOP);
  text(
    'Kaleidoscope:\n' +
    '• Drag to draw\n' +
    '• Up/Down: symmetry = ' + symmetry + '\n' +
    '• Press C to clear',
    20, 18
  );
}
Line-by-line explanation (21 lines)

🔧 Subcomponents:

function-call Semi-transparent Background background(0, 0, 0, 8);

Instead of fully erasing, this draws a mostly-transparent black layer that fades old strokes while keeping them visible as glowing trails

function-call Center Coordinate System translate(width / 2, height / 2);

Moves the origin (0,0) to the center of the canvas so rotation happens around the middle point

conditional Mouse Press Detection if (mouseIsPressed) {

Only draws when the user is actively pressing the mouse button

calculation Distance and Weight Mapping const d = dist(0, 0, mx, my); const sw = map(d, 0, min(width, height) / 2, 12, 1);

Calculates how far the mouse is from center and maps that distance to stroke weight—closer to center = thicker line

calculation Dynamic Hue Cycling const hue = (frameCount * 0.5 + d * 0.2) % 360;

Combines frame count (for animation) and distance (for spatial variety) to create a hue that changes over time and with mouse position

for-loop Rotation and Mirroring Loop for (let i = 0; i < symmetry; i++) { rotate(angleStep); line(mx, my, pmx, pmy); push(); scale(1, -1); line(mx, my, pmx, pmy); pop(); }

Rotates the coordinate system and draws the stroke multiple times, then flips vertically to create mirror symmetry, repeating symmetry times

drawing-block Instruction Panel rect(10, 10, 260, 60, 8);

Draws a semi-transparent background box for the on-screen instructions text

background(0, 0, 0, 8);
Draws a nearly-transparent black rectangle that slightly darkens everything. Since 8 is very low opacity, strokes from previous frames remain visible but fade gradually, creating the glowing trail effect.
translate(width / 2, height / 2);
Moves the origin (0,0) to the center of the canvas. Now when you rotate(), it spins around the middle instead of the top-left corner.
if (mouseIsPressed) {
p5.js built-in variable that is true only while the mouse button is held down. This ensures strokes only appear when actively dragging.
const mx = mouseX - width / 2;
Converts the mouse's absolute position on screen to a position relative to the canvas center (which is now at 0,0 due to translate)
const pmx = pmouseX - width / 2;
Previous mouse X—stores the mouse position from last frame, also centered. Used to draw a line from where the mouse was to where it is now.
const d = dist(0, 0, mx, my);
Calculates the straight-line distance from the center (0,0) to the current mouse position. Farther from center = larger d.
const sw = map(d, 0, min(width, height) / 2, 12, 1);
Maps the distance d to a stroke weight: at the center (d=0) the weight is 12 pixels, at the canvas edge it shrinks to 1 pixel. This creates thicker lines near the center.
const hue = (frameCount * 0.5 + d * 0.2) % 360;
Creates a hue value that changes every frame (frameCount increases by 1 each frame) and also varies with distance from center. The % 360 wraps the value back to 0 when it exceeds 360, creating infinite cycling.
stroke(hue, 80, 100, 100);
Sets the stroke color: the calculated hue, 80% saturation (vivid), 100% brightness (fully lit), 100% opacity (fully opaque). In HSB mode, this creates a bright, saturated color from the current hue.
const angleStep = TWO_PI / symmetry;
Calculates the angle to rotate each time through the loop. TWO_PI is 2π radians (a full circle), so dividing by symmetry gives equal slices. For symmetry=10, each step rotates 2π/10 = ~36 degrees.
for (let i = 0; i < symmetry; i++) {
Loop that repeats 'symmetry' times (default 10). Each iteration draws the stroke at a new rotation.
rotate(angleStep);
Rotates the entire coordinate system by angleStep radians. After this line, all drawings are tilted. Next iteration rotates further, accumulating the rotation.
line(mx, my, pmx, pmy);
Draws a line from the previous mouse position to the current mouse position, in the current rotated coordinate system. This is the original stroke.
push();
Saves the current transformation state (current rotation). Everything after this is a new layer of transformations.
scale(1, -1);
Flips the coordinate system vertically (scales y by -1). Lines drawn now are mirrored across the x-axis.
line(mx, my, pmx, pmy);
Draws the same line again, but in the flipped coordinate system. This creates the mirror image.
pop();
Restores the transformation state saved by push(), removing the scale(1, -1) flip so the next iteration starts fresh.
resetMatrix();
Cancels all transformations (translate, rotate, scale). Returns to the default coordinate system so text can be drawn in the normal top-left corner.
fill(0, 0, 0, 40);
Sets the fill color to a very dark, semi-transparent black (0 hue, 0 saturation, 0 brightness, 40% opacity). Used for the instruction panel background.
rect(10, 10, 260, 60, 8);
Draws a rounded rectangle at position (10, 10) with width 260 and height 60; the 8 is the corner radius for rounding. This creates the instruction panel box.
fill(0, 0, 100, 90);
Sets fill to white in HSB (0 hue, 0 saturation, 100 brightness) with 90% opacity. Used for the instruction text.

keyPressed()

keyPressed() is a p5.js function that automatically runs whenever ANY key is pressed. It's useful for handling keyboard input without constant checking. Here it lets users toggle the number of symmetrical slices and clear the canvas instantly.

🔬 These lines change symmetry by ±1 and keep it between 2 and 32. What if you changed the min/max from 2, 32 to 1, 64? You'd allow more extreme patterns—try it and see if symmetry=1 (no mirroring) or symmetry=64 (tiny slices) creates interesting effects.

  } else if (keyCode === UP_ARROW) {
    symmetry = constrain(symmetry + 1, 2, 32);
  } else if (keyCode === DOWN_ARROW) {
    symmetry = constrain(symmetry - 1, 2, 32);
function keyPressed() {
  if (key === 'c' || key === 'C') {
    // Clear instantly
    background(0, 0, 0, 100);
  } else if (keyCode === UP_ARROW) {
    symmetry = constrain(symmetry + 1, 2, 32);
  } else if (keyCode === DOWN_ARROW) {
    symmetry = constrain(symmetry - 1, 2, 32);
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

conditional Clear Command if (key === 'c' || key === 'C') {

Checks if the user pressed C (uppercase or lowercase)

conditional Increase Symmetry } else if (keyCode === UP_ARROW) {

Detects the up arrow key to increase the number of symmetrical slices

conditional Decrease Symmetry } else if (keyCode === DOWN_ARROW) {

Detects the down arrow key to decrease the number of symmetrical slices

if (key === 'c' || key === 'C') {
Checks if the key pressed was lowercase 'c' OR uppercase 'C'. The || operator means 'or'.
background(0, 0, 0, 100);
Draws a fully opaque black background (alpha 100), instantly erasing all previous strokes and resetting the canvas to blank.
} else if (keyCode === UP_ARROW) {
keyCode is a p5.js variable that stores the numeric code of the key pressed. UP_ARROW is a constant for the up arrow key. This checks if it was pressed.
symmetry = constrain(symmetry + 1, 2, 32);
Increases symmetry by 1, then constrains it to stay between 2 and 32. constrain() ensures the value never goes below 2 or above 32, preventing weird or broken patterns.
} else if (keyCode === DOWN_ARROW) {
Checks if the down arrow key was pressed.
symmetry = constrain(symmetry - 1, 2, 32);
Decreases symmetry by 1 and constrains it to the same 2-32 range. This ensures you can't make symmetry too small or too large.

windowResized()

windowResized() is a special p5.js function that automatically runs whenever the browser window is resized. Without it, the canvas would stay its original size when you resize the window. Here we update the canvas size and clear it with a fresh background.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  background(0, 0, 0, 100);
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
p5.js detects when the browser window changes size and updates the canvas to match the new window dimensions.
background(0, 0, 0, 100);
Clears the canvas with a fully opaque black background after resizing. This prevents stretched or distorted artwork when the window size changes.

📦 Key Variables

symmetry number

Controls how many mirrored slices radiate from the center. Higher values create denser geometric patterns with more reflections.

let symmetry = 10;
mx, my number

Current mouse position relative to the canvas center (converted from absolute screen coordinates)

const mx = mouseX - width / 2;
pmx, pmy number

Previous frame's mouse position relative to the canvas center, used to draw a line segment from last frame to this frame

const pmx = pmouseX - width / 2;
d number

Distance from the canvas center to the current mouse position, used to control stroke weight and hue

const d = dist(0, 0, mx, my);
sw number

Stroke weight (thickness in pixels) mapped from distance—thicker near the center, thinner at the edges

const sw = map(d, 0, min(width, height) / 2, 12, 1);
hue number

Current hue value (0-360) that cycles over time and varies with distance, creating animated rainbow colors

const hue = (frameCount * 0.5 + d * 0.2) % 360;
angleStep number

The angle in radians to rotate between each mirrored copy (2π divided by symmetry value)

const angleStep = TWO_PI / symmetry;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE draw() - symmetry loop

The loop rotates the entire coordinate system, which accumulates rotation every iteration. If symmetry is large (32), many rotate() calls stack up and could slow frame rate on older devices.

💡 Use push() and pop() within the loop to isolate each rotation: wrap the entire drawing block in push(); rotate(i * angleStep); ... pop(); so each iteration starts fresh instead of accumulating rotations. This is clearer and potentially faster.

BUG draw() - coordinate system

The translate(width / 2, height / 2) persists across multiple drawing calls within the if-block. While it's reset by resetMatrix(), if any code accidentally draws between the translate and resetMatrix, it might appear in the wrong place.

💡 Wrap the kaleidoscope drawing logic (translate through the symmetry loop) in push() and pop() to isolate its transformations and make the intent clearer.

STYLE keyPressed() - arrow key handling

The UP_ARROW and DOWN_ARROW checks are separate if-else conditions checking keyCode, but the logic could be clearer with early returns or a switch statement for better readability.

💡 Refactor to use a switch statement on keyCode, or use early return patterns to make the intent of each key handler more obvious.

FEATURE draw() - color randomization

The hue is deterministic and tied to frameCount and distance; there's no random color variation, so patterns can feel predictable after repeated use.

💡 Add an optional random offset to the hue calculation like random(0, 10) that changes on mouseDown, or allow the user to press a key to randomize color palettes while keeping the same pattern.

🔄 Code Flow

Code flow showing setup, draw, keypressed, windowresized

💡 Click on function names in the diagram to jump to their code

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> trail-background[trail-background] draw --> color-mode[color-mode] draw --> coordinate-translation[coordinate-translation] draw --> mouse-input-check[mouse-input-check] mouse-input-check -->|Yes| distance-calculation[distance-calculation] distance-calculation --> hue-calculation[hue-calculation] hue-calculation --> symmetry-loop[symmetry-loop] symmetry-loop -->|Repeat symmetry times| symmetry-loop draw --> ui-panel[ui-panel] draw -->|No| draw draw --> clear-check[clear-check] clear-check -->|C pressed| draw draw --> up-arrow-check[up-arrow-check] up-arrow-check -->|Up Arrow pressed| draw draw --> down-arrow-check[down-arrow-check] down-arrow-check -->|Down Arrow pressed| draw setup --> canvas-creation[canvas-creation] click setup href "#fn-setup" click draw href "#fn-draw" click canvas-creation href "#sub-canvas-creation" click color-mode href "#sub-color-mode" click trail-background href "#sub-trail-background" click coordinate-translation href "#sub-coordinate-translation" click mouse-input-check href "#sub-mouse-input-check" click distance-calculation href "#sub-distance-calculation" click hue-calculation href "#sub-hue-calculation" click symmetry-loop href "#sub-symmetry-loop" click ui-panel href "#sub-ui-panel" click clear-check href "#sub-clear-check" click up-arrow-check href "#sub-up-arrow-check" click down-arrow-check href "#sub-down-arrow-check"

❓ Frequently Asked Questions

What type of visual patterns does the p5.js sketch create?

The sketch creates glowing, animated kaleidoscope patterns that mirror and trail out from the center, producing colorful and intricate designs.

How can users interact with the kaleidoscope sketch?

Users can paint by dragging their mouse across the canvas, adjust the symmetry of the design using the arrow keys, and clear the canvas by pressing the 'C' key.

What creative coding concepts are demonstrated in this sketch?

This sketch showcases techniques such as symmetry in graphics, real-time color manipulation, and the use of mouse interactions to create dynamic visual art.

Preview

for web wensday on corbun does weird webs - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of for web wensday on corbun does weird webs - Code flow showing setup, draw, keypressed, windowresized
Code Flow Diagram