Bouncing Blended Balls

This sketch creates an interactive canvas filled with 15 semi-transparent colored circles that bounce off the walls and each other. When circles overlap, their colors blend together using additive color mixing, creating vibrant new hues. You can drag your mouse across the canvas to push the circles around.

🧪 Try This!

Experiment with the code by making these changes:

  1. Make circles bigger — Larger circles overlap more frequently and create more complex blended colors across the canvas
  2. Add more circles — More circles fill the screen with more activity and create a denser web of overlapping colors
  3. Make circles faster — Higher base speed creates more collision and bouncing activity, with faster visual changes
  4. Switch to subtractive blending — Change ADD blend mode to MULTIPLY to see how overlapping circles darken instead of brighten
  5. Make circles fully opaque — Changing alpha from 0.3 to 1.0 removes transparency, so overlapping circles completely cover what's behind them instead of blending
  6. Increase mouse interaction strength — Higher shoveStrength makes circles fly farther when you drag the mouse across them
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch animates multiple semi-transparent circles that continuously bounce around the canvas and respond to your mouse dragging. The visual magic comes from additive color blending: when circles overlap, their colors add together to create brighter, entirely new colors—purples and blues combine into cyan, for example. It combines several core p5.js techniques: object-oriented programming with a custom Circle class, the draw loop for animation, blendMode() for color effects, and mouse interaction through the mouseDragged() function.

The code is organized into setup() which initializes the canvas and creates 15 Circle objects, draw() which updates and displays every circle each frame, and helper functions like mouseDragged() and windowResized() that handle interaction and responsive sizing. By studying it you will learn how to organize complex sketches using classes, how blendMode() transforms overlapping shapes, and how to make interactive animations that respond to user input.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas, switches to HSB color mode for easier color control, and calls initializeCircles() which generates 15 Circle objects with random positions, velocities, radii, and trendy hues (purples, blues, greens, and pinks)
  2. Every frame, draw() first paints a semi-transparent black rectangle over the entire canvas, creating a subtle motion trail effect
  3. Then it switches the blend mode to ADD, which makes colors add together when shapes overlap instead of covering each other
  4. For each circle, the code calls move() to update its position based on velocity, bounce() to reverse its direction when it hits an edge, and display() to draw it with its semi-transparent color
  5. When you drag the mouse, mouseDragged() calculates how far the mouse moved, finds all circles within mouseRadius pixels, and pushes them in the direction you're dragging by adjusting their velocity
  6. When the window resizes, windowResized() adjusts the canvas and re-initializes all circles so they stay within bounds

🎓 Concepts You'll Learn

Object-oriented programming with ES6 classesAnimation using the draw loop and velocityCollision detection and wall bouncingAdditive color blending with blendMode()Mouse interaction and event handlingHSB color mode for transparency controlArray iteration and manipulation

📝 Code Breakdown

setup()

setup() runs exactly once when the sketch starts. It is the place to initialize your canvas, set colors and styles, and populate data structures. In this sketch, it prepares everything before draw() begins its infinite loop.

function setup() {
  createCanvas(windowWidth, windowHeight);
  // Use HSB color mode for easier transparency and color selection
  // Hue: 0-360, Saturation: 0-100, Brightness: 0-100, Alpha: 0-1
  colorMode(HSB, 360, 100, 100, 1);
  noStroke(); // No outlines for the circles
  initializeCircles(); // Create our initial set of circles
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

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

Creates a canvas that fills the entire browser window

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

Switches to HSB (hue, saturation, brightness) with alpha transparency, making colors and blending more intuitive

function-call Visual Styling noStroke();

Removes outlines from shapes so circles appear smooth without borders

function-call Circle Initialization initializeCircles();

Populates the circles array with 15 new Circle objects

createCanvas(windowWidth, windowHeight);
Creates a p5.js canvas that matches the full width and height of the browser window, making the sketch responsive
colorMode(HSB, 360, 100, 100, 1);
Switches from the default RGB color mode to HSB, where hue ranges 0-360 (the color spectrum), saturation and brightness range 0-100, and alpha ranges 0-1 for transparency—this makes creating trendy color palettes much simpler
noStroke();
Tells p5.js not to draw outlines around shapes, so the circles will have clean, soft edges
initializeCircles();
Calls the custom initializeCircles() function to create and populate the circles array with 15 new Circle objects, each with random properties

initializeCircles()

This function is called during setup() and again when the window resizes. It demonstrates several important patterns: clearing and repopulating arrays, using loops to create multiple objects, calculating safe spawn positions, and using conditional logic (the ternary operator ? :) to pick between two values based on a random test.

🔬 These four ranges define the trendy color palette. What happens if you replace all four with random(0, 360) so hues are totally unpredictable instead of curated?

    let trendyHues = [
      random(260, 300), // Purples
      random(200, 240), // Blues
      random(120, 160), // Greens
      random(320, 360)  // Pinks/Reds
    ];
function initializeCircles() {
  circles = []; // Clear any existing circles
  for (let i = 0; i < numCircles; i++) {
    // Generate random radius within a range
    let r = baseRadius + random(-maxRadiusVariance, maxRadiusVariance);
    // Generate random position within canvas bounds, accounting for radius
    let x = random(r, width - r);
    let y = random(r, height - r);
    // Generate random velocity, ensuring a minimum speed
    let vx = random(-maxSpeedVariance, maxSpeedVariance) + (random() > 0.5 ? baseSpeed : -baseSpeed);
    let vy = random(-maxSpeedVariance, maxSpeedVariance) + (random() > 0.5 ? baseSpeed : -baseSpeed);
    
    // Choose a trendy hue from a predefined range
    let trendyHues = [
      random(260, 300), // Purples
      random(200, 240), // Blues
      random(120, 160), // Greens
      random(320, 360)  // Pinks/Reds
    ];
    let hue = random(trendyHues);
    let saturation = 70; // Moderate saturation
    let brightness = 90; // Bright but not fully white
    let alpha = 0.3; // Significant transparency
    let c = color(hue, saturation, brightness, alpha);
    
    // Add a new Circle object to our array
    circles.push(new Circle(x, y, vx, vy, r, c));
  }
}
Line-by-line explanation (12 lines)

🔧 Subcomponents:

for-loop Circle Creation Loop for (let i = 0; i < numCircles; i++) {

Repeats the circle creation process numCircles times (15 times by default)

calculation Random Radius Generation let r = baseRadius + random(-maxRadiusVariance, maxRadiusVariance);

Creates slight size variation by adding a random offset to the base radius, so circles are not all identical

calculation Safe Position Generation let x = random(r, width - r); let y = random(r, height - r);

Places circles randomly within the canvas while ensuring they don't spawn partially off-screen

calculation Velocity with Minimum Speed let vx = random(-maxSpeedVariance, maxSpeedVariance) + (random() > 0.5 ? baseSpeed : -baseSpeed);

Creates velocity that always has a minimum speed in one direction, plus random variance, ensuring circles don't move too slowly

calculation Trendy Color Palette let trendyHues = [ random(260, 300), // Purples random(200, 240), // Blues random(120, 160), // Greens random(320, 360) // Pinks/Reds ]; let hue = random(trendyHues);

Selects from predefined trendy hue ranges instead of allowing any hue, creating a cohesive color scheme

function-call Circle Object Creation circles.push(new Circle(x, y, vx, vy, r, c));

Instantiates a new Circle object with all generated properties and adds it to the circles array

circles = [];
Clears the circles array to an empty state, discarding any previously created circles and starting fresh
for (let i = 0; i < numCircles; i++) {
Starts a loop that will run numCircles times (15 by default), creating one circle per iteration
let r = baseRadius + random(-maxRadiusVariance, maxRadiusVariance);
Generates a random radius by starting with baseRadius (60) and adding a random offset between -40 and 40, resulting in radii ranging from 20 to 100 pixels
let x = random(r, width - r);
Picks a random x position between r and width-r, ensuring the circle's edge doesn't spawn outside the canvas left or right
let y = random(r, height - r);
Picks a random y position between r and height-r, ensuring the circle's edge doesn't spawn outside the canvas top or bottom
let vx = random(-maxSpeedVariance, maxSpeedVariance) + (random() > 0.5 ? baseSpeed : -baseSpeed);
Creates horizontal velocity: adds a random variance (-2 to 2) to either +4 or -4, so the circle always has meaningful movement with slight randomness
let vy = random(-maxSpeedVariance, maxSpeedVariance) + (random() > 0.5 ? baseSpeed : -baseSpeed);
Creates vertical velocity using the same logic as vx, ensuring circles move in all directions with consistent energy
let trendyHues = [ random(260, 300), // Purples random(200, 240), // Blues random(120, 160), // Greens random(320, 360) // Pinks/Reds ];
Creates an array of four trendy hue values—each a random value within a specific color range—so we can pick from a curated palette
let hue = random(trendyHues);
Picks one random hue from the trendy array, selecting a color that fits the trendy scheme
let saturation = 70; // Moderate saturation let brightness = 90; // Bright but not fully white let alpha = 0.3; // Significant transparency
Sets fixed values for saturation (moderately vibrant), brightness (bright but not washed out), and alpha (30% opaque, 70% transparent) so all circles have a consistent look
let c = color(hue, saturation, brightness, alpha);
Creates a p5.js color object using the HSB values and transparency, producing the actual color that will be used to fill each circle
circles.push(new Circle(x, y, vx, vy, r, c));
Creates a new Circle object with all the generated properties and adds it to the end of the circles array using push()

draw()

draw() is the heartbeat of every p5.js sketch—it runs 60 times per second by default. In this sketch, it maintains the motion trail effect by redrawing a fading background, switches to an additive blend mode to make colors magical when they overlap, updates every circle, and then resets the blend mode. This function is where all the animation happens.

🔬 This loop calls three methods on each circle in sequence. What happens if you comment out circle.bounce() so circles never bounce and eventually disappear off-screen?

  for (let circle of circles) {
    circle.move();
    circle.bounce();
    circle.display();
  }
function draw() {
  // Draw a slightly less transparent background for a clearer trailing effect
  // This helps the editor's heuristic detect canvas changes more easily.
  fill(0, 0, 0, 0.1); // CHANGE: Slightly less transparent (was 0.05)
  rect(0, 0, width, height);

  // Set blend mode to ADD. When shapes overlap, their colors are added together,
  // creating brighter, unique blended colors.
  blendMode(ADD);

  // Update and display each circle
  for (let circle of circles) {
    circle.move();
    circle.bounce();
    circle.display();
  }

  // Reset blend mode to default (BLEND) for any subsequent drawing,
  // though none exists in this sketch.
  blendMode(BLEND);
}
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Motion Trail Background fill(0, 0, 0, 0.1); rect(0, 0, width, height);

Paints a semi-transparent black rectangle over the entire canvas each frame, creating a fading motion trail effect

function-call Additive Blend Mode blendMode(ADD);

Changes how overlapping colors combine—instead of one covering the other, they add together to create brighter blended hues

for-loop Circle Update Loop for (let circle of circles) { circle.move(); circle.bounce(); circle.display(); }

Iterates through every circle and updates its position, checks for bounces, and draws it

function-call Blend Mode Reset blendMode(BLEND);

Resets blend mode to the default so any future drawing won't use additive blending

fill(0, 0, 0, 0.1);
Sets the fill color to black with alpha 0.1 (10% opaque, 90% transparent) in HSB mode. This will be used for the background rectangle.
rect(0, 0, width, height);
Draws a rectangle from the top-left corner (0,0) to the bottom-right corner (width, height), covering the entire canvas with the semi-transparent black color
blendMode(ADD);
Switches to ADD blend mode, which makes colors add together where shapes overlap. Red + Blue = Magenta, Blue + Green = Cyan, etc., creating the magical color effect
for (let circle of circles) {
Starts a for-of loop that iterates through each Circle object in the circles array
circle.move();
Calls the move() method on the current circle, which updates its x and y position based on its velocity
circle.bounce();
Calls the bounce() method on the current circle, which checks if it hit any canvas edge and reverses its velocity if needed
circle.display();
Calls the display() method on the current circle, which draws it on the canvas using its color, position, and radius
blendMode(BLEND);
Resets blend mode back to the default BLEND mode, so any drawing after the loop won't use additive blending (though this sketch doesn't draw anything after the loop)

mouseDragged()

mouseDragged() is a p5.js event handler that fires whenever the user drags the mouse. It demonstrates several key techniques: using pmouseX/pmouseY to calculate motion deltas, distance calculations for spatial queries, and velocity-based interaction. This is the core of the interactivity in this sketch.

🔬 This conditional only pushes circles within mouseRadius pixels of the mouse. What happens if you change the condition to `if (d > mouseRadius + circle.r)` so only DISTANT circles get pushed?

  for (let circle of circles) {
    // Calculate distance from mouse to circle center
    let d = dist(mouseX, mouseY, circle.x, circle.y);

    // If the mouse is within the circle's interaction radius (mouseRadius + circle's own radius)
    if (d < mouseRadius + circle.r) {
function mouseDragged() {
  // Calculate the mouse movement vector since the last frame
  let dx = mouseX - pmouseX;
  let dy = mouseY - pmouseY;

  // If there's no movement, or if pmouse values are not yet valid (first drag frame)
  // This prevents applying shove when the mouse isn't actually moving or on the very first frame of a drag
  if ((dx === 0 && dy === 0) || (pmouseX === 0 && pmouseY === 0 && mouseX !== 0 && mouseY !== 0)) {
    return;
  }

  for (let circle of circles) {
    // Calculate distance from mouse to circle center
    let d = dist(mouseX, mouseY, circle.x, circle.y);

    // If the mouse is within the circle's interaction radius (mouseRadius + circle's own radius)
    if (d < mouseRadius + circle.r) {
      // Apply a shove force based on mouse movement
      circle.vx += dx * shoveStrength;
      circle.vy += dy * shoveStrength;

      // Constrain velocity to prevent circles from flying too fast
      circle.vx = constrain(circle.vx, -maxVelocity, maxVelocity);
      circle.vy = constrain(circle.y, -maxVelocity, maxVelocity); // BUG FIX: Changed circle.y to circle.vy
    }
  }
}
Line-by-line explanation (11 lines)

🔧 Subcomponents:

calculation Mouse Movement Delta let dx = mouseX - pmouseX; let dy = mouseY - pmouseY;

Calculates how far the mouse moved since the last frame by comparing current and previous positions

conditional Movement Validation Check if ((dx === 0 && dy === 0) || (pmouseX === 0 && pmouseY === 0 && mouseX !== 0 && mouseY !== 0)) { return; }

Prevents pushing circles when the mouse didn't actually move or on the first frame of a drag when previous position data isn't valid

for-loop Circle Interaction Loop for (let circle of circles) {

Iterates through every circle to check if it should be affected by the mouse movement

calculation Distance from Mouse to Circle let d = dist(mouseX, mouseY, circle.x, circle.y);

Calculates the Euclidean distance from the mouse to each circle's center

conditional Interaction Radius Check if (d < mouseRadius + circle.r) {

Checks if the circle is within the interaction zone (100 pixels around the mouse plus the circle's own radius)

calculation Velocity Modification circle.vx += dx * shoveStrength; circle.vy += dy * shoveStrength;

Applies a force to the circle's velocity proportional to how fast the mouse is moving and the shoveStrength multiplier

calculation Velocity Clamping circle.vx = constrain(circle.vx, -maxVelocity, maxVelocity); circle.vy = constrain(circle.y, -maxVelocity, maxVelocity);

Clamps velocity to a maximum range so circles don't fly off the screen, though vy line contains a bug

let dx = mouseX - pmouseX;
Calculates the change in mouse x position since the last frame by subtracting the previous x (pmouseX) from the current x (mouseX)
let dy = mouseY - pmouseY;
Calculates the change in mouse y position since the last frame by subtracting the previous y (pmouseY) from the current y (mouseY)
if ((dx === 0 && dy === 0) || (pmouseX === 0 && pmouseY === 0 && mouseX !== 0 && mouseY !== 0)) {
This condition returns early (does nothing) if either: (1) the mouse didn't move (dx and dy are both zero), or (2) this is the first frame of dragging where pmouse values haven't been set yet (pmouseX and pmouseY are both zero but mouseX/Y are not)
return;
Exits the function immediately, skipping all the circle-pushing logic if the guard condition above is true
for (let circle of circles) {
Starts a for-of loop to check every circle in the circles array
let d = dist(mouseX, mouseY, circle.x, circle.y);
Uses p5.js's built-in dist() function to calculate the Euclidean distance from the mouse position to this circle's center position
if (d < mouseRadius + circle.r) {
Checks if the distance is less than the sum of the interaction radius (100 pixels) and the circle's own radius—if true, the mouse is close enough to affect this circle
circle.vx += dx * shoveStrength;
Adds a scaled version of the mouse's horizontal movement to the circle's horizontal velocity, pushing it in the direction the mouse moved
circle.vy += dy * shoveStrength;
Adds a scaled version of the mouse's vertical movement to the circle's vertical velocity
circle.vx = constrain(circle.vx, -maxVelocity, maxVelocity);
Clamps the vx velocity to stay between -15 and 15, preventing the circle from moving too fast horizontally
circle.vy = constrain(circle.y, -maxVelocity, maxVelocity);
This line contains a bug: it constrains circle.y (position, not velocity) instead of circle.vy. It should read circle.vy to properly cap vertical velocity

windowResized()

windowResized() is a p5.js event handler that fires automatically whenever the browser window is resized. It ensures the sketch adapts gracefully to window size changes by resizing the canvas and re-initializing circles so they stay properly positioned. This makes the sketch truly responsive.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Re-initialize circles for the new canvas size to prevent them from
  // being out of bounds or resizing incorrectly.
  initializeCircles();
}
Line-by-line explanation (2 lines)

🔧 Subcomponents:

function-call Canvas Resize resizeCanvas(windowWidth, windowHeight);

Updates the p5.js canvas to match the new window dimensions

function-call Circle Re-initialization initializeCircles();

Regenerates all circles with new random properties and positions safe for the new canvas size

resizeCanvas(windowWidth, windowHeight);
Calls p5.js's resizeCanvas() function to update the canvas to match the current window width and height, handling responsive sizing
initializeCircles();
Re-runs the initializeCircles() function to create 15 new circles with random properties and positions that respect the new canvas bounds

Circle (class)

The Circle class demonstrates object-oriented programming in p5.js. Each Circle object stores its own state (position, velocity, radius, color) and methods to act on that state (move, bounce, display). This makes the code modular and reusable—you can create dozens of Circle objects and each one manages itself. The constructor is called when you execute new Circle(...), and methods are called using dot notation like circle.move() or circle.display().

🔬 This code bounces the circle off left/right edges. Notice it reverses velocity AND uses constrain() to push the circle back. What happens if you comment out the constrain() line so circles can get stuck partially off-screen?

    // Bounce off left/right edges
    if (this.x - this.r < 0 || this.x + this.r > width) {
      this.vx *= -1; // Reverse x velocity
      // Prevent the circle from sticking to the edge
      this.x = constrain(this.x, this.r, width - this.r);
    }
class Circle {
  constructor(x, y, vx, vy, r, c) {
    this.x = x;
    this.y = y;
    this.vx = vx; // Velocity in x direction
    this.vy = vy; // Velocity in y direction
    this.r = r;   // Radius
    this.c = c;   // Color
  }

  // Move the circle based on its velocity
  move() {
    this.x += this.vx;
    this.y += this.vy;
  }

  // Check for collisions with canvas edges and reverse velocity to bounce
  bounce() {
    // Bounce off left/right edges
    if (this.x - this.r < 0 || this.x + this.r > width) {
      this.vx *= -1; // Reverse x velocity
      // Prevent the circle from sticking to the edge
      this.x = constrain(this.x, this.r, width - this.r);
    }
    // Bounce off top/bottom edges
    if (this.y - this.r < 0 || this.y + this.r > height) {
      this.vy *= -1; // Reverse y velocity
      // Prevent the circle from sticking to the edge
      this.y = constrain(this.y, this.r, height - this.r);
    }
  }

  // Display the circle
  display() {
    fill(this.c); // Set the circle's transparent color
    ellipse(this.x, this.y, this.r * 2); // Draw the circle
  }
}
Line-by-line explanation (20 lines)

🔧 Subcomponents:

function Constructor constructor(x, y, vx, vy, r, c) {

Initializes a new Circle object with position, velocity, radius, and color

function Move Method move() { this.x += this.vx; this.y += this.vy; }

Updates the circle's position by adding its velocity to its coordinates

function Bounce Method bounce() {

Checks if the circle has hit any canvas edge and reverses its velocity to bounce back

conditional Horizontal Edge Bounce if (this.x - this.r < 0 || this.x + this.r > width) { this.vx *= -1; this.x = constrain(this.x, this.r, width - this.r); }

Detects if the circle crossed the left or right canvas edge and bounces it back

conditional Vertical Edge Bounce if (this.y - this.r < 0 || this.y + this.r > height) { this.vy *= -1; this.y = constrain(this.y, this.r, height - this.r); }

Detects if the circle crossed the top or bottom canvas edge and bounces it back

function Display Method display() { fill(this.c); ellipse(this.x, this.y, this.r * 2); }

Draws the circle at its current position with its color

constructor(x, y, vx, vy, r, c) {
Defines the constructor function that runs when you create a new Circle object with new Circle(...)
this.x = x;
Assigns the x parameter to the object's x property, storing the circle's horizontal position
this.y = y;
Assigns the y parameter to the object's y property, storing the circle's vertical position
this.vx = vx; // Velocity in x direction
Assigns the vx parameter to the object's vx property, storing how fast the circle moves horizontally
this.vy = vy; // Velocity in y direction
Assigns the vy parameter to the object's vy property, storing how fast the circle moves vertically
this.r = r; // Radius
Assigns the r parameter to the object's r property, storing the circle's radius
this.c = c; // Color
Assigns the c parameter to the object's c property, storing the circle's fill color
move() {
Defines a method called move() that updates the circle's position
this.x += this.vx;
Adds the circle's horizontal velocity to its x position, moving it right or left
this.y += this.vy;
Adds the circle's vertical velocity to its y position, moving it down or up
bounce() {
Defines a method called bounce() that checks for and handles wall collisions
if (this.x - this.r < 0 || this.x + this.r > width) {
Checks if the circle's left edge (x - r) went past the left canvas border or right edge (x + r) went past the right border
this.vx *= -1;
Reverses the horizontal velocity by multiplying it by -1, so positive becomes negative and vice versa
this.x = constrain(this.x, this.r, width - this.r);
Clamps the x position back into the safe range, preventing the circle from getting stuck partially off-screen
if (this.y - this.r < 0 || this.y + this.r > height) {
Checks if the circle's top edge (y - r) went past the top canvas border or bottom edge (y + r) went past the bottom border
this.vy *= -1;
Reverses the vertical velocity by multiplying it by -1
this.y = constrain(this.y, this.r, height - this.r);
Clamps the y position back into the safe range
display() {
Defines a method called display() that draws the circle on the canvas
fill(this.c);
Sets the fill color to this circle's color, so the next shape drawn will use this color
ellipse(this.x, this.y, this.r * 2);
Draws an ellipse (circle) at position (this.x, this.y) with diameter this.r * 2 (diameter = 2 × radius)

📦 Key Variables

circles array

An array that stores all Circle objects in the sketch. Modified by initializeCircles() and iterated through in draw() and mouseDragged().

let circles = [];
numCircles number

Controls how many Circle objects are created. Used in the for loop inside initializeCircles().

let numCircles = 15;
baseRadius number

The starting radius value before random variance is applied. Each circle's actual radius varies around this base value.

let baseRadius = 60;
maxRadiusVariance number

The maximum amount a circle's radius can differ from baseRadius. Creates radii ranging from baseRadius - maxRadiusVariance to baseRadius + maxRadiusVariance.

let maxRadiusVariance = 40;
baseSpeed number

The minimum speed magnitude for each circle's velocity. All circles move at least this fast in at least one direction.

let baseSpeed = 4;
maxSpeedVariance number

The maximum random variance added to baseSpeed when generating velocity. Creates speed variation between circles.

let maxSpeedVariance = 2;
mouseRadius number

The radius in pixels around the mouse within which circles will respond to dragging. Circles further away are unaffected.

let mouseRadius = 100;
shoveStrength number

A multiplier that scales how much mouse movement affects circle velocity. Higher values make circles more sensitive to dragging.

let shoveStrength = 0.25;
maxVelocity number

The speed cap for circles. Velocities are constrained to stay between -maxVelocity and +maxVelocity, preventing circles from flying off the canvas.

let maxVelocity = 15;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

BUG mouseDragged() function, line with constrain for vy

The line `circle.vy = constrain(circle.y, -maxVelocity, maxVelocity)` constrains circle.y (position) instead of circle.vy (velocity), so vertical velocity is never actually limited. This allows circles to accelerate unboundedly when dragged vertically.

💡 Change the line to `circle.vy = constrain(circle.vy, -maxVelocity, maxVelocity);` to properly cap vertical velocity just like horizontal velocity is capped on the line above.

PERFORMANCE initializeCircles() function

The trendyHues array is recreated inside the loop for every circle (15 times), even though the hues are random each time. This wastes computation.

💡 Move the trendyHues array outside the loop or pre-generate the hue ranges once per call to avoid redundant array creation. Alternatively, generate random hues directly without the intermediate array.

FEATURE draw() function

The sketch always uses blendMode(ADD) for every circle, which is visually striking but limits creative variation. Different blend modes create different aesthetics.

💡 Consider allowing users to change blend modes via keyboard input (e.g., press 'A' for ADD, 'M' for MULTIPLY, 'S' for SCREEN) to let them explore different visual effects without editing code.

STYLE initializeCircles() function

The code selects a random element from the trendyHues array, but if trendyHues only has 4 elements (one per color range), it's simpler to just pick a random color range directly without creating an intermediate array.

💡 Simplify to: `let colorRanges = [[260, 300], [200, 240], [120, 160], [320, 360]]; let range = random(colorRanges); let hue = random(range[0], range[1]);` for clearer intent.

🔄 Code Flow

Code flow showing setup, initializecircles, draw, mousedragged, windowresized, circle

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

graph TD start[Start] --> setup[setup] setup --> initializecircles[initializeCircles] initializecircles --> canvas-creation[Canvas Setup] initializecircles --> color-mode[Color Mode Configuration] initializecircles --> styling[Visual Styling] initializecircles --> initialization[Circle Initialization] initialization --> loop-over-circles[Circle Creation Loop] loop-over-circles --> radius-generation[Random Radius Generation] loop-over-circles --> position-generation[Safe Position Generation] loop-over-circles --> velocity-generation[Velocity with Minimum Speed] loop-over-circles --> hue-selection[Trendy Color Palette] loop-over-circles --> circle-creation[Circle Object Creation] circle-creation --> circle[Circle] setup --> draw[draw loop] draw --> background-fade[Motion Trail Background] draw --> blend-mode-add[Additive Blend Mode] draw --> circle-update-loop[Circle Update Loop] circle-update-loop --> circle[Circle] circle-update-loop --> blend-mode-reset[Blend Mode Reset] draw --> mousedragged[mouseDragged] mousedragged --> mouse-delta[Mouse Movement Delta] mousedragged --> movement-guard[Movement Validation Check] movement-guard --> circle-interaction-loop[Circle Interaction Loop] circle-interaction-loop --> distance-calculation[Distance from Mouse to Circle] circle-interaction-loop --> interaction-radius-check[Interaction Radius Check] interaction-radius-check --> velocity-application[Velocity Modification] velocity-application --> velocity-clamping[Velocity Clamping] draw --> windowresized[windowResized] windowresized --> canvas-resize[Canvas Resize] windowresized --> circle-reinit[Circle Re-initialization] click setup href "#fn-setup" click initializecircles href "#fn-initializecircles" click draw href "#fn-draw" click mousedragged href "#fn-mousedragged" click windowresized href "#fn-windowresized" click circle href "#fn-circle" click canvas-creation href "#sub-canvas-creation" click color-mode href "#sub-color-mode" click styling href "#sub-styling" click initialization href "#sub-initialization" click loop-over-circles href "#sub-loop-over-circles" click radius-generation href "#sub-radius-generation" click position-generation href "#sub-position-generation" click velocity-generation href "#sub-velocity-generation" click hue-selection href "#sub-hue-selection" click circle-creation href "#sub-circle-creation" click background-fade href "#sub-background-fade" click blend-mode-add href "#sub-blend-mode-add" click circle-update-loop href "#sub-circle-update-loop" click blend-mode-reset href "#sub-blend-mode-reset" click mouse-delta href "#sub-mouse-delta" click movement-guard href "#sub-movement-guard" click circle-interaction-loop href "#sub-circle-interaction-loop" click distance-calculation href "#sub-distance-calculation" click interaction-radius-check href "#sub-interaction-radius-check" click velocity-application href "#sub-velocity-application" click velocity-clamping href "#sub-velocity-clamping" click canvas-resize href "#sub-canvas-resize" click circle-reinit href "#sub-circle-reinit" click constructor href "#sub-constructor" click move-method href "#sub-move-method" click bounce-method href "#sub-bounce-method" click horizontal-bounce href "#sub-horizontal-bounce" click vertical-bounce href "#sub-vertical-bounce" click display-method href "#sub-display-method"

❓ Frequently Asked Questions

What visual experience does the Bouncing Blended Balls sketch provide?

The sketch creates a dynamic visual experience with colorful, bouncing circles that overlap and blend together as they hit the perimeter of the canvas.

How can users interact with the Bouncing Blended Balls sketch?

Users can interact with the sketch by moving their mouse, which affects the movement of the circles within a specified radius, pushing them away.

What creative coding techniques are demonstrated in the Bouncing Blended Balls sketch?

This sketch demonstrates techniques such as random radius and velocity generation, collision detection with screen edges, and HSB color manipulation for visual effects.

Preview

Bouncing Blended Balls - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Bouncing Blended Balls - Code flow showing setup, initializecircles, draw, mousedragged, windowresized, circle
Code Flow Diagram