flower

This sketch creates an interactive kaleidoscope drawing experience where dragging the mouse paints symmetrical, glowing patterns with radial mirroring. The canvas displays a dreamy gradient background with particle trails that fade and swirl, turning simple brush strokes into mesmerizing mandala-like designs.

🧪 Try This!

Experiment with the code by making these changes:

  1. Add trail persistence — Lower the alpha in the background() call to make brush strokes fade much more slowly, creating long ghostly trails
  2. Make a spirograph effect
  3. Explode particles — Increase particle velocity dramatically so they burst outward faster and more chaotically
  4. Rainbow speed boost — Colors cycle faster through the spectrum as you draw, creating more vibrant rapid hue shifts
  5. Thin glowing lines — Reduce stroke weight multiplier so lines are always delicate and thin, emphasizing the glow effect
  6. Darker, moodier background — Reverse the gradient brightness so the center is light and edges are dark, creating an inverted color scheme
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch transforms your mouse into a kaleidoscope brush, painting mirrored lines that radiate around the center of the screen while leaving behind fading particle trails. It combines several powerful p5.js techniques: the HSB color mode for smooth hue cycling, matrix transforms (translate, rotate, scale, push/pop) for radial symmetry, and a graphics buffer for a pre-computed gradient background that persists across frames. The result is hypnotic and responsive—dragging feels painterly, and the symmetry count can be adjusted on the fly with arrow keys.

The code is organized around three main ideas: a Particle class that simulates short-lived, fading emitters; a drawBgGradient() function that builds a reusable background image once per setup or resize; and a draw loop that handles symmetry transformations, layered stroke effects, and keyboard interaction. By studying this sketch you will learn how to use classes for complex behavior, how matrix transforms create kaleidoscopic repetition, and how to balance visual richness with performance using particle limits and graphics buffers.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, sets color mode to HSB (Hue, Saturation, Brightness) for easy color cycling, and builds a radial gradient background image stored in bgGradient. This gradient is drawn once and reused, rather than recalculated every frame.
  2. Every frame, draw() clears the canvas lightly (with 8% alpha) to create a trailing effect, then draws the pre-computed gradient on top. The coordinate system is translated to the canvas center so rotation becomes radial symmetry.
  3. When the mouse is pressed, the code calculates the mouse's distance from center and uses it to map stroke weight, hue (which also cycles based on frameCount), saturation, and brightness. This creates responsive, distance-sensitive color and line width.
  4. For each frame of drawing, the code rotates through all symmetry slices, drawing a line in each slice and its vertical mirror using scale(1, -1)—this creates the kaleidoscope effect. It draws two layers: a thicker, darker, semi-transparent stroke beneath a thinner, brighter, more opaque stroke on top, creating a neon glow illusion.
  5. Particles are spawned along the drawn path, each with random velocity and a fading lifetime. They are updated each frame and removed when their alpha reaches zero, keeping particle count below the performance limit of 300.
  6. Keyboard input allows users to press C to clear everything or arrow keys to increase/decrease the symmetry count (2 to 32 slices), instantly changing the pattern complexity. Window resizing rebuilds the gradient buffer and clears particles to keep the experience responsive on any screen size.

🎓 Concepts You'll Learn

Radial symmetry and matrix transformsHSB color mode and hue cyclingClass-based particle systemsGraphics buffers and performance optimizationInteractive mouse drawing with state trackingLayered stroke effects for neon glow

📝 Code Breakdown

setup()

setup() runs once when the sketch loads. Here we initialize the canvas size, color mode, and pre-compute expensive graphics like the gradient background so they don't need to be recalculated every frame.

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Use HSB so we can easily cycle through hues
  colorMode(HSB, 360, 100, 100, 100);

  // Create a graphics buffer for the background gradient
  bgGradient = createGraphics(width, height);
  drawBgGradient(); // Draw the gradient once

  background(0, 0, 100, 100); // Start with a solid white background (initial clear)
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Full-screen canvas creation createCanvas(windowWidth, windowHeight);

Creates a canvas that fills the entire browser window, making the experience immersive

calculation HSB color mode initialization colorMode(HSB, 360, 100, 100, 100);

Switches to Hue-Saturation-Brightness mode with hue 0–360, making color cycling smooth and intuitive

calculation Graphics buffer creation bgGradient = createGraphics(width, height);

Creates an off-screen graphics object where the background gradient is drawn once and reused, improving performance

createCanvas(windowWidth, windowHeight);
Creates a canvas the size of the browser window so the kaleidoscope fills your entire screen
colorMode(HSB, 360, 100, 100, 100);
Switches color mode to HSB so hues cycle smoothly from 0–360; saturation and brightness range 0–100 each
bgGradient = createGraphics(width, height);
Creates an off-screen graphics buffer the same size as the canvas where we will draw the background gradient
drawBgGradient(); // Draw the gradient once
Calls the function that draws concentric circles to create the radial gradient, done once at startup for efficiency
background(0, 0, 100, 100); // Start with a solid white background (initial clear)
Fills the canvas with white (hue 0, sat 0, bright 100) to start with a clean slate

draw()

draw() is the heart of the sketch, running 60 times per second. It handles symmetry transformations via rotate() and scale() to create the kaleidoscope effect, calculates distance-sensitive colors and stroke weights, draws two layered strokes for a glow effect, and manages the particle system. Understanding the for-loops that rotate through symmetry slices is key to grasping how kaleidoscopes work in code.

🔬 This creates 2 particles per frame to add density to the trails. What happens if you increase this to 5 or 10? What if you decrease it to 0 to turn off particles entirely?

      for (let i = 0; i < 2; i++) { // Add 2 particles per frame when drawing
        particles.push(new Particle(mx, my, hue, sat, bright));
      }
function draw() {
  // Keep the semi-transparent background for the trailing effect
  background(0, 0, 100, 8);
  // Draw the pre-computed static background gradient behind the trails
  image(bgGradient, 0, 0);

  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 (faster cycling for more vibrancy)
    const hue = (frameCount * 1.2 + d * 0.3) % 360;

    // Vary saturation and brightness based on distance from center for more vibrant strokes
    const sat = map(d, 0, min(width, height) / 2, 100, 40); // Full sat near center, more muted at edges
    const bright = map(d, 0, min(width, height) / 2, 100, 70); // Full bright near center, slightly darker at edges

    noFill();

    // Draw two strokes for a layered effect (neon glow)
    const angleStep = TWO_PI / symmetry;

    // 1st stroke: Thicker, slightly darker, and more transparent
    strokeWeight(sw * 1.5);
    stroke(hue, sat, bright * 0.7, 30); // 30% alpha

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

    // 2nd stroke: Thinner, brighter, and less transparent (drawn on top)
    strokeWeight(sw);
    stroke(hue, sat, bright, 70); // 70% alpha

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

    // Add particles along the drawing path
    if (particles.length < maxParticles) {
      for (let i = 0; i < 2; i++) { // Add 2 particles per frame when drawing
        particles.push(new Particle(mx, my, hue, sat, bright));
      }}
  }

  // Update and display particles
  for (let i = particles.length - 1; i >= 0; i--) {
    particles[i].update();
    particles[i].display();
    if (particles[i].alpha <= 0) {
      particles.splice(i, 1); // Remove dead particles
    }
  }

  // Reset transform to draw UI text in the top-left corner
  resetMatrix();
}
Line-by-line explanation (17 lines)

🔧 Subcomponents:

conditional Mouse press detection if (mouseIsPressed) {

Only draws and creates particles when the user is actively clicking and dragging

calculation Distance from center 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 for responsive, distance-sensitive lines

calculation Hue and color calculation const hue = (frameCount * 1.2 + d * 0.3) % 360;

Creates smoothly cycling rainbow colors that also vary with distance from center, giving visual depth

for-loop Layered stroke effect (background) 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 a thicker, semi-transparent stroke layer rotated symmetry times to create the kaleidoscope base

for-loop Layered stroke effect (highlight) 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 a thinner, brighter, more opaque layer on top to create a neon glow effect

for-loop Particle animation loop for (let i = particles.length - 1; i >= 0; i--) { particles[i].update(); particles[i].display(); if (particles[i].alpha <= 0) { particles.splice(i, 1); // Remove dead particles } }

Updates each particle's position and fade, drawing it on screen, and removes dead particles from the array

background(0, 0, 100, 8);
Fills the canvas with a nearly transparent white (8% alpha), creating a trailing effect where old brush strokes fade gradually
image(bgGradient, 0, 0);
Draws the pre-computed gradient background image at the top-left corner, creating a dreamy backdrop
translate(width / 2, height / 2); // center the coordinate system
Moves the origin (0,0) to the center of the canvas so rotation happens around the middle, not the top-left corner
const mx = mouseX - width / 2;
Converts the global mouse position to coordinates relative to the center (after translate)
const d = dist(0, 0, mx, my);
Calculates the Euclidean distance from the canvas center to the current mouse position
const sw = map(d, 0, min(width, height) / 2, 12, 1);
Maps distance to stroke weight: near center (d=0) gives thick lines (12), far away gives thin lines (1)
const hue = (frameCount * 1.2 + d * 0.3) % 360;
Creates a hue that cycles over time (frameCount * 1.2) and also shifts with distance (d * 0.3), modulo 360 to wrap back to red
const sat = map(d, 0, min(width, height) / 2, 100, 40);
Makes colors near center fully saturated (100) and edges more muted (40) for a focus effect
const angleStep = TWO_PI / symmetry;
Divides a full rotation (TWO_PI radians) by the symmetry count to get the angle between each mirrored slice
stroke(hue, sat, bright * 0.7, 30); // 30% alpha
Sets stroke color to the calculated hue with dimmer brightness (bright * 0.7) and low opacity (30%) for the background layer
for (let i = 0; i < symmetry; i++) {
Loops once for each symmetry slice, rotating the coordinate system each time
rotate(angleStep);
Rotates the entire coordinate system by one symmetry slice angle, so the next line() is drawn at a different angle
scale(1, -1);
Flips the Y axis vertically, creating a mirror image of the line without rotating further
particles.push(new Particle(mx, my, hue, sat, bright));
Creates a new Particle object at the current mouse position with the current color values and adds it to the array
if (particles[i].alpha <= 0) {
Checks if the particle has fully faded out (alpha reached zero or below)
particles.splice(i, 1); // Remove dead particles
Removes the dead particle from the array so it stops being drawn and updated, freeing memory
resetMatrix();
Resets the coordinate system back to normal (top-left origin) so any UI elements drawn after this won't be translated or rotated

drawBgGradient()

drawBgGradient() is a helper function that pre-computes the background image once during setup and again after window resize. It uses concentric circles with map() to smoothly transition colors, then applies a blur filter to create a soft, dreamy backdrop. Drawing this once and reusing it (via image()) every frame is much faster than recalculating it constantly.

🔬 These three lines define the color gradient from center to edge. What happens if you swap the values: e.g., map(r, 0, maxDim, 260, 200) to reverse the hue direction? Or map(r, 0, maxDim, 95, 50) to invert brightness so edges are darker?

    const gradientHue = map(r, 0, maxDim, 200, 260);
    const gradientSat = map(r, 0, maxDim, 80, 20);
    const gradientBright = map(r, 0, maxDim, 50, 95);
function drawBgGradient() {
  bgGradient.colorMode(HSB, 360, 100, 100, 100);
  bgGradient.noStroke();

  // Draw a radial gradient using concentric circles
  const maxDim = max(width, height);
  for (let r = 0; r <= maxDim; r += 2) {
    // Hue, saturation, and brightness change with distance from center
    const gradientHue = map(r, 0, maxDim, 200, 260);
    const gradientSat = map(r, 0, maxDim, 80, 20);
    const gradientBright = map(r, 0, maxDim, 50, 95);

    bgGradient.fill(gradientHue, gradientSat, gradientBright);
    bgGradient.ellipse(width / 2, height / 2, r * 2);
  }
  // Apply a blur filter to smooth out the gradient (done only once on setup/resize)
  bgGradient.filter(BLUR, 8);
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

for-loop Concentric circle loop for (let r = 0; r <= maxDim; r += 2) { const gradientHue = map(r, 0, maxDim, 200, 260); const gradientSat = map(r, 0, maxDim, 80, 20); const gradientBright = map(r, 0, maxDim, 50, 95); bgGradient.fill(gradientHue, gradientSat, gradientBright); bgGradient.ellipse(width / 2, height / 2, r * 2); }

Draws concentric circles from the center outward, each with a slightly different color to create a smooth radial gradient effect

calculation Gaussian blur filter bgGradient.filter(BLUR, 8);

Smooths the banded gradient into a soft, dreamy blend so the concentric circles are no longer visible

bgGradient.colorMode(HSB, 360, 100, 100, 100);
Sets the color mode for the graphics buffer to HSB so we can smoothly transition hues
bgGradient.noStroke();
Disables stroke for all shapes drawn in this buffer, so only the filled circles are visible
const maxDim = max(width, height);
Gets the larger of width or height to ensure circles cover the entire canvas even on very wide or tall screens
for (let r = 0; r <= maxDim; r += 2) {
Loops from radius 0 to maxDim in steps of 2 pixels, drawing one circle per iteration for density
const gradientHue = map(r, 0, maxDim, 200, 260);
Maps the radius to a hue: at center (r=0) hue is 200 (cyan/blue), at edge (r=maxDim) hue is 260 (purple)
const gradientSat = map(r, 0, maxDim, 80, 20);
Maps radius to saturation: center is more vibrant (80), edges are more washed out (20)
const gradientBright = map(r, 0, maxDim, 50, 95);
Maps radius to brightness: center is darker (50), edges are lighter (95), creating a spotlight effect
bgGradient.fill(gradientHue, gradientSat, gradientBright);
Sets the fill color for the graphics buffer based on the calculated hue, saturation, and brightness
bgGradient.ellipse(width / 2, height / 2, r * 2);
Draws a circle at the center of the buffer with diameter r * 2, layering circles from small to large
bgGradient.filter(BLUR, 8);
Applies a Gaussian blur with radius 8 to the entire gradient buffer, smoothing the concentric bands into a soft gradient

keyPressed()

keyPressed() is a p5.js event function that runs once every time the user presses a key. It uses key to check for character input (like 'c') and keyCode to check for special keys like arrows. This function lets you add interactive controls without waiting for mouse input, making your sketch feel responsive to the keyboard.

🔬 These lines let you adjust symmetry with arrow keys. What if you changed the increment from 1 to 5? Or what if you added a third condition: else if (keyCode === LEFT_ARROW) to trigger a different action?

  } 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, then redraw the background gradient and clear particles
    background(0, 0, 100, 100);
    drawBgGradient(); // Redraw static gradient
    particles = []; // Clear all particles
  } 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 (8 lines)

🔧 Subcomponents:

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

Detects when the user presses C (uppercase or lowercase) to trigger a full canvas clear

conditional Increase symmetry on UP arrow else if (keyCode === UP_ARROW) {

Detects the UP arrow key press to increase the symmetry count

conditional Decrease symmetry on DOWN arrow else if (keyCode === DOWN_ARROW) {

Detects the DOWN arrow key press to decrease the symmetry count

if (key === 'c' || key === 'C') {
Checks if the pressed key is lowercase c OR uppercase C (both work)
background(0, 0, 100, 100);
Fills the entire canvas with opaque white (100% alpha), erasing all drawn content instantly
drawBgGradient(); // Redraw static gradient
Re-computes the gradient background image to ensure it's fresh and unmodified
particles = []; // Clear all particles
Empties the particles array, removing all fading particle trails from the screen
else if (keyCode === UP_ARROW) {
Checks if the pressed key is the UP arrow key
symmetry = constrain(symmetry + 1, 2, 32);
Increases symmetry by 1, but limits it between 2 and 32 so it never goes below 2 mirrors or above 32
else if (keyCode === DOWN_ARROW) {
Checks if the pressed key is the DOWN arrow key
symmetry = constrain(symmetry - 1, 2, 32);
Decreases symmetry by 1, keeping it between the same 2–32 bounds

windowResized()

windowResized() is a p5.js event function that automatically runs whenever the browser window is resized. It ensures the canvas fills the window at all times and rebuilds the gradient background to fit the new dimensions. Without this function, the sketch would have black bars on resize.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Reinitialize and redraw the background gradient on resize
  bgGradient = createGraphics(width, height);
  drawBgGradient();
  background(0, 0, 100, 100); // Clear to solid white after resize
  particles = []; // Clear particles on resize
}
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Canvas resize resizeCanvas(windowWidth, windowHeight);

Adjusts the canvas size to match the new browser window dimensions

calculation Gradient buffer rebuild bgGradient = createGraphics(width, height); drawBgGradient();

Recreates and redraws the gradient background to fit the new canvas size

resizeCanvas(windowWidth, windowHeight);
Resizes the p5.js canvas to match the new browser window dimensions
bgGradient = createGraphics(width, height);
Creates a new graphics buffer at the new size (the old one would be mismatched)
drawBgGradient();
Redraws the gradient onto the new buffer so it covers the new canvas size
background(0, 0, 100, 100); // Clear to solid white after resize
Fills the canvas with white to clear any old content and start fresh after the resize
particles = []; // Clear particles on resize
Empties the particles array to reset the animation state when the window changes size

Particle class

The Particle class is a reusable blueprint that encapsulates all the properties and behaviors of a single particle. Each particle has position, velocity, color, and an alpha value that decreases over its lifetime. The update() method moves it and fades it, and display() draws it. Using a class keeps particle logic clean and makes it easy to create hundreds of independent particles.

🔬 These two lines give each particle a random velocity in x and y. What if you changed them so particles always move outward from center instead of randomly? Try: this.vx = this.x * 0.02; this.vy = this.y * 0.02; to see them radiate outward.

    this.vx = random(-1.5, 1.5);
    this.vy = random(-1.5, 1.5);
class Particle {
  constructor(x, y, hue, sat, bright) {
    this.x = x;
    this.y = y;
    // Random velocity for spread, biased slightly outward from center
    this.vx = random(-1.5, 1.5);
    this.vy = random(-1.5, 1.5);
    this.hue = hue;
    this.sat = sat;
    this.bright = bright;
    this.alpha = 100; // Start fully opaque
    this.lifetime = random(30, 90); // Frames to live (0.5 to 1.5 seconds at 60fps)
    this.radius = random(1, 4); // Small particle size
  }

  update() {
    this.x += this.vx;
    this.y += this.vy;
    this.alpha -= 100 / this.lifetime; // Fade out over lifetime
  }

  display() {
    noStroke();
    fill(this.hue, this.sat, this.bright, this.alpha);
    ellipse(this.x, this.y, this.radius * 2);
  }
}
Line-by-line explanation (10 lines)

🔧 Subcomponents:

calculation Particle constructor constructor(x, y, hue, sat, bright) {

Initializes a new particle with position, color, velocity, and lifespan

calculation Update particle state update() { this.x += this.vx; this.y += this.vy; this.alpha -= 100 / this.lifetime; }

Moves the particle and fades it out gradually over its lifetime

calculation Draw particle on canvas display() { noStroke(); fill(this.hue, this.sat, this.bright, this.alpha); ellipse(this.x, this.y, this.radius * 2); }

Renders the particle as a small circle with the current alpha (transparency)

class Particle {
Defines a class (a blueprint) for creating particle objects with shared properties and methods
constructor(x, y, hue, sat, bright) {
The constructor runs when a new Particle is created, accepting position and color values
this.x = x;
Stores the particle's initial x position as a property of this object
this.vx = random(-1.5, 1.5);
Assigns a random horizontal velocity between -1.5 and 1.5 pixels per frame for varied motion
this.lifetime = random(30, 90); // Frames to live (0.5 to 1.5 seconds at 60fps)
Each particle lives between 30 and 90 frames (0.5 to 1.5 seconds at 60 fps) before fully fading
this.alpha = 100; // Start fully opaque
Initializes opacity to 100 (fully visible); alpha will decrease each frame in update()
this.x += this.vx;
Moves the particle by adding its velocity to its position each frame
this.alpha -= 100 / this.lifetime;
Reduces alpha each frame by dividing 100 (full opacity) by the lifetime so it fully fades by the end
fill(this.hue, this.sat, this.bright, this.alpha);
Sets the fill color using the particle's stored hue, saturation, and brightness with its current alpha (transparency)
ellipse(this.x, this.y, this.radius * 2);
Draws a small circle at the particle's current position with diameter this.radius * 2

📦 Key Variables

symmetry number

Controls how many mirrored slices appear in the kaleidoscope; adjustable with arrow keys from 2 to 32

let symmetry = 10;
bgGradient object (p5.Graphics)

Stores a pre-drawn radial gradient background image that is reused every frame for performance

let bgGradient; // created in setup()
particles array of Particle objects

Holds all active particles; particles are added when drawing and removed when their alpha reaches zero

let particles = [];
maxParticles number

Performance limit: no more than this many particles can exist at once to keep the sketch smooth

let maxParticles = 300;

🔧 Potential Improvements (6)

Here are some ways this code could be enhanced:

PERFORMANCE draw() - particle loop

Removing particles from the array using splice() in a loop can cause missed particles; iterating backward fixes this but the current code iterates backward correctly. However, creating 2 particles every frame when drawing could exceed maxParticles if not checked carefully.

💡 Move the particle.length < maxParticles check inside the inner loop: for (let i = 0; i < 2; i++) { if (particles.length < maxParticles) { particles.push(...) } } to guarantee the cap is always respected.

BUG draw() - rotation accumulation

The rotate() function is called multiple times in succession without resetting, so rotations accumulate. Each loop iteration rotates further, which is intentional for the kaleidoscope effect, but if angleStep is calculated incorrectly, patterns can misalign.

💡 Add a push() before the rotation loop and pop() after to isolate transformations: push(); for (let i = 0; i < symmetry; i++) { ... } pop(); This prevents accidental interference with other code.

STYLE Particle class - constructor

The constructor accepts hue, sat, bright as separate parameters, but these could be passed as a color object or array to reduce parameter count and improve readability.

💡 Refactor to: constructor(x, y, colorArray) { this.hue = colorArray[0]; this.sat = colorArray[1]; this.bright = colorArray[2]; } and pass [hue, sat, bright] when creating particles.

FEATURE keyPressed()

Only C, UP, and DOWN keys are implemented; no way to save, reset stroke thickness, or adjust particle count from the keyboard.

💡 Add more keyboard shortcuts: 'S' to save as PNG (saveCanvas()), 'R' to reset all variables to defaults, '+/-' to adjust maxParticles in real-time.

BUG windowResized()

Clearing particles and the canvas on resize is jarring; if a user resizes mid-draw, their work disappears.

💡 Instead of clearing particles, scale their positions: particles.forEach(p => { p.x *= newWidth / oldWidth; p.y *= newHeight / oldHeight; }). Only clear if the resize is very drastic (e.g., more than 50% change).

PERFORMANCE draw() - background image

Calling image(bgGradient, 0, 0) every frame adds overhead, especially on large canvases. The gradient could be drawn directly to the main canvas once per resize.

💡 Instead of storing bgGradient and calling image() every frame, integrate drawBgGradient() logic directly into the main canvas during setup/resize, reducing one image() call per frame.

🔄 Code Flow

Code flow showing setup, draw, drawbggradient, keypressed, windowresized, particle

💡 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 --> canvas-creation[canvas-creation] draw --> color-mode-setup[color-mode-setup] draw --> gradient-buffer-creation[gradient-buffer-creation] draw --> mouse-drawing-conditional[mouse-drawing-conditional] draw --> distance-calculation[distance-calculation] draw --> hue-cycling-calculation[hue-cycling-calculation] draw --> first-stroke-layer[first-stroke-layer] draw --> second-stroke-layer[second-stroke-layer] draw --> particle-update-loop[particle-update-loop] click canvas-creation href "#sub-canvas-creation" click color-mode-setup href "#sub-color-mode-setup" click gradient-buffer-creation href "#sub-gradient-buffer-creation" click mouse-drawing-conditional href "#sub-mouse-drawing-conditional" click distance-calculation href "#sub-distance-calculation" click hue-cycling-calculation href "#sub-hue-cycling-calculation" click first-stroke-layer href "#sub-first-stroke-layer" click second-stroke-layer href "#sub-second-stroke-layer" click particle-update-loop href "#sub-particle-update-loop" particle-update-loop --> constructor-method[constructor-method] particle-update-loop --> update-method[update-method] particle-update-loop --> display-method[display-method] click constructor-method href "#sub-constructor-method" click update-method href "#sub-update-method" click display-method href "#sub-display-method" draw --> clear-conditional[clear-conditional] draw --> symmetry-up-conditional[symmetry-up-conditional] draw --> symmetry-down-conditional[symmetry-down-conditional] click clear-conditional href "#sub-clear-conditional" click symmetry-up-conditional href "#sub-symmetry-up-conditional" click symmetry-down-conditional href "#sub-symmetry-down-conditional" setup --> drawbggradient[drawBgGradient] click drawbggradient href "#fn-drawbggradient" drawbggradient --> concentric-loop[concentric-loop] drawbggradient --> blur-filter[blur-filter] drawbggradient --> gradient-rebuild[gradient-rebuild] click concentric-loop href "#sub-concentric-loop" click blur-filter href "#sub-blur-filter" click gradient-rebuild href "#sub-gradient-rebuild" setup --> windowresized[windowResized] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What type of visual effects does the flower sketch produce?

The flower sketch creates swirling, kaleidoscopic patterns that mirror brush strokes, generating glowing mandala-like designs against a dreamy radial gradient background.

How can users interact with the flower sketch?

Users can interact by dragging the mouse across the screen, which paints colorful trails and particles that add to the dynamic artwork.

What creative coding concepts does the flower sketch showcase?

This sketch demonstrates techniques such as particle systems, symmetry in drawing, and the creation of radial gradients for visually appealing backgrounds.

Preview

flower - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of flower - Code flow showing setup, draw, drawbggradient, keypressed, windowresized, particle
Code Flow Diagram