for web wensday on corbun does weird webs upd

This sketch transforms your mouse into a symmetrical kaleidoscope brush that paints colorful, mirrored trails around a central image. As you drag, the sketch creates vibrant, swirling patterns with trails that fade and shimmer, combining interactive drawing with mathematical symmetry and HSB color cycling.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make the background fade slower for longer trails — Lower alpha values (the last number in background) make trails persist longer because previous frames are redrawn more opaquely
  2. Increase the maximum symmetry level — Raise the upper constrain limit from 32 to 64 to allow up to 64 mirrored slices, creating intricate mandala-like patterns
  3. Make the center image larger — Increasing the scale multiplier from 0.5 to 1.0 or higher makes the central image more prominent and visible under the kaleidoscope strokes
  4. Make colors cycle through the spectrum much faster — Increase the frameCount multiplier from 0.8 to a higher value like 3.0 to make hues shift much more rapidly as you draw
  5. Invert the stroke weight so far strokes are thicker — Swapping the final two numbers in the map() call reverses the relationship between distance and thickness
  6. Remove the mirrored strokes to see only rotated copies — Deleting the vertical mirror code simplifies the pattern to show only rotations without reflections, cutting the visual symmetry in half
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch creates an interactive kaleidoscope drawing tool powered by p5.js. When you drag your mouse, it paints colorful trails that are mirrored and rotated symmetrically around the center of the canvas—like looking through a real kaleidoscope. A loaded image sits at the center while your brush strokes multiply and shimmer around it, creating mesmerizing spiral patterns. The sketch combines several advanced p5.js techniques: image loading and positioning, the HSB color model for smooth hue cycling, coordinate transformations (translate and rotate), and interactive mouse tracking.

The code is organized into five functions: preload() loads an external image before setup runs, setup() initializes the canvas and color mode, draw() handles all animation and symmetry math, keyPressed() responds to user input, and windowResized() adapts to screen changes. By studying this sketch, you will learn how to load images, use mathematical transformations to create symmetry, cycle through colors smoothly using HSB, and track mouse motion to create interactive drawing tools.

⚙️ How It Works

  1. When the sketch starts, preload() downloads an image from the internet and stores it in the centerImage variable before anything else runs
  2. setup() creates a full-screen canvas, switches to HSB color mode (which makes color cycling easy), and sets imageMode(CENTER) so images draw from their center point
  3. Every frame, draw() clears the background with slight transparency to create fade-out trails, then translates the coordinate system so (0, 0) is now the canvas center
  4. The loaded image is drawn at the center at 50% scale, providing a focal point for the kaleidoscope pattern
  5. When the mouse is pressed, the sketch calculates the distance from center to mouse, which controls both the stroke weight (closer = thicker) and the hue color
  6. A loop runs 'symmetry' times (default 10), rotating the canvas and drawing both normal and vertically-mirrored strokes, creating perfectly symmetrical patterns radiating from center
  7. Pressing arrow keys increases or decreases the symmetry value, changing how many mirrored slices appear; pressing 'C' clears the canvas to white

🎓 Concepts You'll Learn

Image loading with preload()HSB color model and hue cyclingCoordinate transformation (translate and rotate)Mouse interaction and trackingMathematical symmetry and mirroringAlpha blending for fade trails

📝 Code Breakdown

preload()

preload() is a special p5.js function that runs before setup(). Any slow operations like loading images should go here so your sketch doesn't start until everything is ready.

function preload() {
  // Load the image from the provided URL
  // The preload() function ensures the image is fully loaded before setup() runs.
  centerImage = loadImage('https://images.steamusercontent.com/ugc/1688248122333193690/E203434B4344D6B9AEF48513F5F682656647EE83/?imw=512&&ima=fit&impolicy=Letterbox&imcolor=%23000000&letterbox=false');
}
Line-by-line explanation (1 lines)
centerImage = loadImage('https://images.steamusercontent.com/ugc/1688248122333193690/E203434B4344D6B9AEF48513F5F682656647EE83/?imw=512&&ima=fit&impolicy=Letterbox&imcolor=%23000000&letterbox=false');
Downloads the image from the URL and stores it in the centerImage variable. preload() waits for this to complete before setup() runs, preventing the image from appearing blank.

setup()

setup() runs once when the sketch starts. It is where you initialize the canvas size, set up color modes, and configure drawing defaults that stay the same for the entire sketch lifetime.

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Use HSB so we can easily cycle through hues
  colorMode(HSB, 360, 100, 100, 100);
  background(0, 0, 100, 100); // start with solid white (HSB: hue 0, sat 0, brightness 100)

  // Set imageMode to CENTER once in setup().
  // This makes it easy to position images by their center.
  imageMode(CENTER);
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

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

Switches from default RGB to HSB (Hue, Saturation, Brightness) so hue values cycle smoothly from 0-360 degrees

function-call Image Center Positioning imageMode(CENTER);

Makes image() position images by their center point instead of top-left, simplifying centered drawing

createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window, making the sketch responsive to window size
colorMode(HSB, 360, 100, 100, 100);
Switches to HSB color mode where hue ranges 0–360 (like a color wheel), and saturation and brightness each range 0–100. This makes cycling through rainbow colors very natural.
background(0, 0, 100, 100);
Fills the canvas with white: hue 0, saturation 0 (no color), brightness 100 (max light), alpha 100 (fully opaque)
imageMode(CENTER);
Tells p5.js to position images by their center point—without this, images would be positioned by their top-left corner

draw()

draw() runs every frame (60 times per second by default) and is the main animation loop. Every frame it clears (with fading), calculates all the geometry, and redraws. The symmetry loop is the magic—by rotating and drawing the same line multiple times, we create the kaleidoscope illusion.

🔬 This loop draws both a rotated line AND its vertical mirror every iteration. What happens if you delete the lines between 'push()' and 'pop()' (the mirrored stroke)? You'll see only half as many lines in the pattern.

    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();
    }

🔬 These two lines make strokes thicker when the mouse is close to center (d is small). What happens if you swap the 12 and 1? Now close strokes will be thin and far strokes will be thick—the opposite of normal.

    const d = dist(0, 0, mx, my);
    const sw = map(d, 0, min(width, height) / 2, 12, 1);
function draw() {
  // Slightly transparent background for trailing effect (now white)
  background(0, 0, 100, 8);

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

  // Draw the image at the new origin (which is now the center of the canvas)
  // The kaleidoscope lines will be drawn on top of this image.
  // To make it small, we'll draw it at 50% of its original width and height.
  image(centerImage, 0, 0, centerImage.width * 0.5, centerImage.height * 0.5);

  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
    // The image's size will also affect the effective drawing area,
    // so we can adjust the map range if needed.
    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
    // Increased cycle speed slightly for more "brightness" / change
    const hue = (frameCount * 0.8 + d * 0.2) % 360;

    // Make saturation 100 for maximum vibrancy, brightness 100 for maximum brightness
    stroke(hue, 100, 100, 100); // Full saturation, full brightness
    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();

  // Text instructions are removed, so no changes needed here.
}
Line-by-line explanation (23 lines)

🔧 Subcomponents:

function-call Fade-Out Trail Effect background(0, 0, 100, 8);

Redraws background with low alpha (8) instead of opaque (100), so previous strokes gradually fade rather than disappear instantly

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

Moves the origin (0, 0) to the canvas center, making rotation and symmetry calculations much simpler

function-call Draw Center Image image(centerImage, 0, 0, centerImage.width * 0.5, centerImage.height * 0.5);

Draws the loaded image at the center (0, 0) scaled to 50% of its original size

conditional Drawing Only When Mouse Pressed if (mouseIsPressed) {

Only draws kaleidoscope strokes while the user is actively dragging the mouse

calculation Center-Relative Mouse Position const mx = mouseX - width / 2; const my = mouseY - height / 2; const pmx = pmouseX - width / 2; const pmy = pmouseY - height / 2;

Converts actual mouse positions to distances from the canvas center, used for symmetry calculations

calculation Distance-Based Stroke Weight 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, then maps that distance to a stroke weight (close = thicker, far = thinner)

calculation Time and Distance-Based Hue const hue = (frameCount * 0.8 + d * 0.2) % 360;

Creates a hue value that shifts over time (frameCount) and varies with distance (d), wrapping at 360 to loop through the color wheel

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(); }

Draws the mouse stroke 'symmetry' times, rotating each copy and also drawing a vertically mirrored copy, creating the kaleidoscope effect

background(0, 0, 100, 8);
Redraws the background with alpha 8 (almost transparent) each frame. This lets old strokes fade instead of clearing instantly, creating trailing and shimmer effects.
translate(width / 2, height / 2);
Moves the origin point from top-left to the center of the canvas. All future rotations and drawings happen relative to this center point.
image(centerImage, 0, 0, centerImage.width * 0.5, centerImage.height * 0.5);
Draws the loaded image at coordinates (0, 0), which is now the canvas center due to the translate above. The width and height arguments scale it to 50% of original size.
if (mouseIsPressed) {
Checks if the mouse button is currently held down. The entire drawing block only runs while dragging.
const mx = mouseX - width / 2;
Subtracts the canvas center from the actual mouse X position, giving the distance from center (can be negative if left of center).
const my = mouseY - height / 2;
Same as mx but for the Y axis—distance from center vertically.
const pmx = pmouseX - width / 2;
Previous frame's mouse X position relative to center. Used with pmx/pmy to draw a line from where the mouse was last frame to where it is now.
const pmy = pmouseY - height / 2;
Previous frame's mouse Y position relative to center.
const d = dist(0, 0, mx, my);
Calculates the straight-line distance from center (0, 0) to the current mouse position (mx, my).
const sw = map(d, 0, min(width, height) / 2, 12, 1);
Maps the distance d onto a stroke weight: distance 0 (center) becomes weight 12 (thick), distance to edge becomes weight 1 (thin). min(width, height) ensures the range works for all window sizes.
const hue = (frameCount * 0.8 + d * 0.2) % 360;
Creates a hue value that changes over time (frameCount increases each frame) and varies by distance. The % 360 wraps the value back to 0 when it exceeds 360, cycling through the color wheel.
stroke(hue, 100, 100, 100);
Sets the stroke color: the calculated hue with full saturation (100) and full brightness (100), creating vivid colors.
strokeWeight(sw);
Sets the line thickness to the calculated stroke weight sw, which depends on distance from center.
noFill();
Ensures that only the stroke is drawn, not the inside of any shapes (though we're only drawing lines here).
const angleStep = TWO_PI / symmetry;
Divides a full rotation (TWO_PI radians ≈ 6.28 radians = 360°) by the symmetry value. This is how much to rotate each mirrored copy.
for (let i = 0; i < symmetry; i++) {
Loops 'symmetry' times. Each iteration draws a copy of the stroke rotated further around the center.
rotate(angleStep);
Rotates the coordinate system by angleStep. All future drawing is rotated until this is reversed.
line(mx, my, pmx, pmy);
Draws a line from the previous mouse position to the current mouse position. Because the coordinate system is rotated, this line appears at a different angle each iteration.
push();
Saves the current transformation state (rotation, scale, translation) onto a stack so we can restore it later.
scale(1, -1);
Flips the Y axis (vertical mirror). All drawing that follows is reflected vertically.
line(mx, my, pmx, pmy);
Draws the same line again, but because of the vertical flip, it appears mirrored.
pop();
Restores the previous transformation state from the stack, undoing the vertical flip for the next iteration.
resetMatrix();
Clears all transformations (translate, rotate, scale) so the remaining code runs with default coordinates.

keyPressed()

keyPressed() runs once every time the user presses a key. Use it to add keyboard controls for interactive features like toggling modes or adjusting parameters.

function keyPressed() {
  if (key === 'c' || key === 'C') {
    // Clear instantly (now to solid white)
    background(0, 0, 100, 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 Canvas on 'C' Key if (key === 'c' || key === 'C') { background(0, 0, 100, 100); }

When the user presses 'c' or 'C', fill the entire canvas with white to erase all drawings

conditional Increase Symmetry on UP Arrow else if (keyCode === UP_ARROW) { symmetry = constrain(symmetry + 1, 2, 32); }

UP arrow increases symmetry by 1, clamped to stay between 2 and 32

conditional Decrease Symmetry on DOWN Arrow else if (keyCode === DOWN_ARROW) { symmetry = constrain(symmetry - 1, 2, 32); }

DOWN arrow decreases symmetry by 1, clamped to stay between 2 and 32

if (key === 'c' || key === 'C') {
Checks if the user pressed the 'c' key (lowercase) or 'C' key (uppercase).
background(0, 0, 100, 100);
Fills the entire canvas with opaque white, erasing all previous strokes.
} else if (keyCode === UP_ARROW) {
Checks if the user pressed the UP arrow key. keyCode is a numeric code, UP_ARROW is a p5.js constant for the up arrow.
symmetry = constrain(symmetry + 1, 2, 32);
Increases symmetry by 1, then constrains it to stay between 2 and 32. This prevents the user from setting symmetry too low (less than 2) or too high (more than 32).
} else if (keyCode === DOWN_ARROW) {
Checks if the user pressed the DOWN arrow key.
symmetry = constrain(symmetry - 1, 2, 32);
Decreases symmetry by 1, then constrains it to stay between 2 and 32.

windowResized()

windowResized() is a special p5.js function that runs automatically whenever the browser window changes size. Always call resizeCanvas() in this function to keep your sketch responsive.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  background(0, 0, 100, 100); // Clear to solid white on resize
}
Line-by-line explanation (2 lines)
resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new window dimensions whenever the browser window is resized.
background(0, 0, 100, 100);
Clears the canvas to white after resizing, preventing stretched or distorted old drawings.

📦 Key Variables

symmetry number

Controls how many mirrored slices are drawn in the kaleidoscope pattern. Higher values create more petals. Modified by arrow keys, constrained between 2 and 32.

let symmetry = 10;
centerImage p5.Image object

Stores the image loaded from the internet URL. Drawn at the center of the canvas as the focal point for the kaleidoscope pattern.

let centerImage;
mx number

The current mouse X position relative to the canvas center (can be negative if left of center). Used for calculating rotation and mirroring.

const mx = mouseX - width / 2;
my number

The current mouse Y position relative to the canvas center (can be negative if above center). Used for calculating rotation and mirroring.

const my = mouseY - height / 2;
pmx number

The previous frame's mouse X position relative to the canvas center. Used as the starting point when drawing a line from last frame to this frame.

const pmx = pmouseX - width / 2;
pmy number

The previous frame's mouse Y position relative to the canvas center. Used as the starting point when drawing a line.

const pmy = pmouseY - height / 2;
d number

The distance from the canvas center to the current mouse position. Used to control stroke weight (closer = thicker) and to vary hue with distance.

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

The stroke weight (line thickness) calculated based on distance. Ranges from 1 (thin, far from center) to 12 (thick, at center).

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

The color value (0–360 on the color wheel) that cycles over time and varies with distance. Creates the shimmering color effect.

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

The angle (in radians) to rotate each iteration of the symmetry loop. Calculated as a full rotation (TWO_PI) divided by the symmetry count.

const angleStep = TWO_PI / symmetry;

🔧 Potential Improvements (5)

Here are some ways this code could be enhanced:

PERFORMANCE preload() - image loading

The image is loaded from an external URL on every page load, which may be slow or fail if the server is unavailable

💡 Consider hosting the image locally or using a more reliable CDN. You could also add error handling with loadImage('url', successCallback, failureCallback) to gracefully handle failed loads.

BUG draw() - stroke calculation

When the mouse is exactly at the center, strokeWeight becomes 12 which may be disproportionately large and create harsh visual jumps

💡 Clamp or ease the strokeWeight to prevent extreme values: const sw = map(d, 0, min(width, height) / 2, 8, 1) or use constrain() to smooth transitions.

STYLE draw() - variable naming

The short variable names (mx, my, pmx, pmy, d, sw) are efficient but less readable for beginners

💡 Rename them to: mouseX_centered, mouseY_centered, prevMouseX_centered, prevMouseY_centered, distanceFromCenter, strokeWeight_calculated for better clarity.

FEATURE keyPressed()

There is no on-screen display showing the current symmetry value, so users may not know what symmetry level they are at

💡 Add text display in draw() after resetMatrix(): fill(0); textSize(16); text('Symmetry: ' + symmetry, 10, 20); to show the current value.

FEATURE draw() - color cycling

The hue cycles based on frameCount, but there's no way to toggle or pause the color animation

💡 Add a keyPressed branch for 'p' or spacebar to toggle a boolean variable that controls whether hue should cycle, giving users more control.

🔄 Code Flow

Code flow showing preload, 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] click setup href "#fn-setup" click draw href "#fn-draw" draw --> fade_trails[fade-trails] draw --> coordinate_centering[coordinate-centering] draw --> colormode_hsb[colormode-hsb] draw --> imagemode_center[imagemode-center] draw --> center_image_draw[center-image-draw] draw --> mouse_check[mouse-check] draw --> position_relative_center[position-relative-center] draw --> distance_weight[distance-weight] draw --> hue_cycling[hue-cycling] draw --> symmetry_loop[symmetry-loop] click fade_trails href "#sub-fade-trails" click coordinate_centering href "#sub-coordinate-centering" click colormode_hsb href "#sub-colormode-hsb" click imagemode_center href "#sub-imagemode-center" click center_image_draw href "#sub-center-image-draw" click mouse_check href "#sub-mouse-check" click position_relative_center href "#sub-position-relative-center" click distance_weight href "#sub-distance-weight" click hue_cycling href "#sub-hue-cycling" click symmetry_loop href "#sub-symmetry-loop" mouse_check -->|if pressed| position_relative_center mouse_check -->|if not pressed| draw position_relative_center --> distance_weight distance_weight --> hue_cycling hue_cycling --> symmetry_loop symmetry_loop --> draw keypressed[keypressed] --> clear_key[clear-key] keypressed --> symmetry_up[symmetry-up] keypressed --> symmetry_down[symmetry-down] click keypressed href "#fn-keypressed" click clear_key href "#sub-clear-key" click symmetry_up href "#sub-symmetry-up" click symmetry_down href "#sub-symmetry-down" clear_key -->|if 'C' pressed| draw symmetry_up -->|if UP arrow pressed| draw symmetry_down -->|if DOWN arrow pressed| draw windowresized[windowresized] --> setup click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual effects does the p5.js kaleidoscope sketch create?

The sketch generates vibrant, mirrored trails that swirl around a central image, creating colorful and symmetric patterns that shimmer and fade against a glowing background.

How can users interact with the kaleidoscope brush in this sketch?

Users can drag their mouse across the canvas to paint with the kaleidoscope brush, producing dynamic and colorful designs that respond to their movements.

What creative coding concepts are showcased in this sketch?

This sketch demonstrates the use of symmetry in drawing, real-time interaction through mouse events, and color manipulation using the HSB color mode.

Preview

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