Iridescent Soap Bubbles - xelsed.ai

This sketch creates a continuous stream of translucent soap bubbles that spawn near the bottom of the screen, drift upward with a gentle side-to-side wobble, and pop after a random lifespan. Each bubble is rendered with an animated iridescent radial gradient plus a highlight arc, all sitting on top of a soft blue vertical gradient background.

🧪 Try This!

Experiment with the code by making these changes:

  1. Trigger a bubble blizzard — Cranking up the spawn rate floods the screen with far more bubbles at once, turning the calm scene into a dense bubble storm.
  2. Make bubbles rise faster — Increasing the lerp range for vertical speed makes every bubble zip upward much quicker, giving the scene an energetic, fizzy feel.
  3. Paint a sunset sky — Swapping the background gradient's hex colors instantly changes the mood from a cool blue bubble bath to a warm sunset atmosphere.
Prefer the full editor? Open it there →

📖 About This Sketch

This sketch fills the screen with dozens of soap bubbles that spawn near the bottom, rise upward with a soft wobble, and shimmer with rainbow highlights before fading away. The dreamy iridescent look comes from layering multiple HSLA-based color stops inside a Canvas2D radial gradient, animated frame by frame with millis(), plus a soft blue linear gradient behind everything for the backdrop. It's a great sketch for learning how to combine JavaScript classes, time-based animation, and raw drawingContext gradient calls inside p5.js.

The code is organized around a Bubble class that stores each bubble's size, speed, wobble, color, and lifespan, plus three helper functions that spawn bubbles at a steady rate, update and draw them every frame, and paint the background gradient. Studying it will teach you how to manage a growing-and-shrinking array of objects, how to fade objects in and out smoothly over their lifetime, and how to reach past p5's basic fill()/stroke() system into the underlying HTML5 Canvas API for richer gradients.

⚙️ How It Works

  1. When the sketch loads, setup() creates a full-window canvas and turns off default strokes, while two empty arrays/counters (bubbles and spawnAccumulator) sit ready to track bubble objects.
  2. Every frame, draw() first paints a soft blue gradient background, then calls spawnBubbles() to possibly add new bubbles, then calls updateAndDrawBubbles() to move and render every bubble currently alive.
  3. spawnBubbles() accumulates time using deltaTime so that new Bubble objects appear at a steady average rate (about 3.5 per second) no matter the frame rate, up to a maximum of 80 bubbles on screen.
  4. Each Bubble is given a random size, rise speed, sideways drift, wobble pattern, hue, and lifespan when it's created, biasing toward smaller and slower bubbles for realism.
  5. Every frame, each bubble's update() nudges it upward and sideways, and its display() computes a fade-in/fade-out factor, an animated hue, and a wobbling position, then draws a layered radial gradient, rim stroke, and highlight arc using the raw Canvas2D drawingContext.
  6. When a bubble's age exceeds its random lifespan or it drifts above the top of the screen, isDead() returns true and updateAndDrawBubbles() removes it from the array, effectively 'popping' it.

🎓 Concepts You'll Learn

Object-oriented particle systemsCanvas2D radial and linear gradientsTime-based animation with millis() and deltaTimeFade in/out easing with constrain()Array management (push/splice) for spawning and removing objectsHSLA color animation

📝 Code Breakdown

setup()

setup() runs once when the sketch starts, before draw() begins looping. It's the right place to size the canvas and set drawing defaults that won't change every frame.

function setup() {
  createCanvas(windowWidth, windowHeight); // 2D renderer
  noStroke();
}
Line-by-line explanation (2 lines)
createCanvas(windowWidth, windowHeight); // 2D renderer
Creates a canvas that fills the entire browser window using the current window size.
noStroke();
Turns off shape outlines by default, since bubbles mostly use custom strokes drawn later with explicit stroke() calls.

draw()

draw() is p5's main animation loop, running about 60 times per second. Keeping it short and readable - just three function calls here - makes the whole sketch easy to follow.

function draw() {
  drawBackgroundGradient();
  spawnBubbles();
  updateAndDrawBubbles();
}
Line-by-line explanation (3 lines)
drawBackgroundGradient();
Repaints the soft blue gradient background every frame, which also erases the previous frame's bubbles.
spawnBubbles();
Checks whether enough time has passed to add one or more new bubbles to the bubbles array.
updateAndDrawBubbles();
Moves every existing bubble, draws it, and removes any that have popped.

spawnBubbles()

This function shows the classic 'accumulator' pattern for frame-rate-independent spawning: instead of spawning exactly one bubble per frame (which would depend on frame rate), it tracks fractional time and only spawns when a full unit has built up.

🔬 This loop can spawn several bubbles in a single frame if enough time has accumulated. What happens if you change bubbles.length < MAX_BUBBLES to bubbles.length < 10, giving the sketch a much sparser, calmer feel?

  while (spawnAccumulator >= 1 && bubbles.length < MAX_BUBBLES) {
    bubbles.push(new Bubble());
    spawnAccumulator--;
  }
function spawnBubbles() {
  // Use deltaTime so rate is frame-rate independent
  spawnAccumulator += BUBBLES_PER_SECOND * (deltaTime / 1000);

  while (spawnAccumulator >= 1 && bubbles.length < MAX_BUBBLES) {
    bubbles.push(new Bubble());
    spawnAccumulator--;
  }
}
Line-by-line explanation (4 lines)

🔧 Subcomponents:

while-loop Spawn Accumulator Loop while (spawnAccumulator >= 1 && bubbles.length < MAX_BUBBLES) {

Spawns as many new bubbles as the accumulated time allows, capped by the max bubble count.

spawnAccumulator += BUBBLES_PER_SECOND * (deltaTime / 1000);
Adds a fractional amount of 'bubbles owed' based on how much real time (deltaTime, in milliseconds) passed since the last frame - this keeps the spawn rate consistent even if the frame rate changes.
while (spawnAccumulator >= 1 && bubbles.length < MAX_BUBBLES) {
Keeps spawning bubbles as long as at least one full bubble's worth of time has accumulated and the array isn't already full.
bubbles.push(new Bubble());
Creates a brand new Bubble object (running its constructor) and adds it to the end of the bubbles array.
spawnAccumulator--;
Subtracts 1 from the accumulator since one bubble's worth of 'spawn credit' has just been spent.

updateAndDrawBubbles()

Looping backward while removing items is a common pattern anytime you might splice() elements out of an array mid-iteration - it prevents the classic 'skipped element' bug that happens when indices shift after a removal.

🔬 This loop deliberately counts DOWN (i--) so splice() never skips a bubble. What do you predict would happen visually if you changed it to count up with 'let i = 0; i < bubbles.length; i++'?

  for (let i = bubbles.length - 1; i >= 0; i--) {
    const b = bubbles[i];
    b.update();
    b.display();
    if (b.isDead()) {
      bubbles.splice(i, 1); // "pop" – remove from array
    }
  }
function updateAndDrawBubbles() {
  for (let i = bubbles.length - 1; i >= 0; i--) {
    const b = bubbles[i];
    b.update();
    b.display();
    if (b.isDead()) {
      bubbles.splice(i, 1); // "pop" – remove from array
    }
  }
}
Line-by-line explanation (6 lines)

🔧 Subcomponents:

for-loop Backward Bubble Loop for (let i = bubbles.length - 1; i >= 0; i--) {

Iterates over every bubble from last to first so items can be safely removed mid-loop.

conditional Pop Check if (b.isDead()) {

Removes a bubble from the array once it has expired or floated off screen.

for (let i = bubbles.length - 1; i >= 0; i--) {
Loops through the bubbles array backward (from the last element to the first) so that removing an item with splice() doesn't skip the next bubble.
const b = bubbles[i];
Grabs a reference to the current bubble object so we can call its methods.
b.update();
Moves this bubble upward and sideways by one frame's worth of motion.
b.display();
Draws this bubble's gradient, rim, and highlight at its current position.
if (b.isDead()) {
Checks whether this bubble's lifespan has ended or it has drifted off the top of the screen.
bubbles.splice(i, 1); // "pop" – remove from array
Removes exactly one item at index i from the array, effectively popping the bubble out of existence.

Bubble constructor

A class constructor runs once when 'new Bubble()' is called, setting up all the randomized properties that make each bubble unique - size, position, speed, wobble, color, and lifespan.

🔬 Raising random() to a power above 1 skews values toward 0, favoring small bubbles. What happens if you change 1.4 to 0.4, which biases toward LARGER bubbles instead?

    const t = pow(random(), 1.4); // bias toward smaller sizes
    this.radius = lerp(minR, maxR, t);
constructor() {
    // Radius: more small than big for variation
    const minR = 16;
    const maxR = 70;
    const t = pow(random(), 1.4); // bias toward smaller sizes
    this.radius = lerp(minR, maxR, t);

    // Spawn near bottom center with some horizontal spread
    const spread = width * 0.35;
    this.x = width / 2 + random(-spread, spread);
    this.y = height + random(10, 80); // start just below bottom

    // Vertical speed (rise)
    this.vy = -lerp(0.7, 1.6, 1 - t); // smaller bubbles a bit slower

    // Slow horizontal drift
    this.vxBase = random(-0.15, 0.15);

    // Wobble: horizontal sinusoidal offset
    this.wobbleAmp = random(5, 18);
    this.wobbleFreq = random(0.3, 0.8); // cycles per second
    this.phase = random(TWO_PI);

    // Lifetime for popping
    this.birth = millis();
    this.lifespan = random(8000, 18000); // 8–18 seconds

    // Iridescent color + transparency
    this.baseHue = random(0, 360);
    this.baseAlpha = random(0.30, 0.55);

    // Sheen highlight drift around the bubble
    this.sheenSpeed = random(-0.6, 0.6); // radians per second
  }
Line-by-line explanation (8 lines)

🔧 Subcomponents:

calculation Size Bias Calculation const t = pow(random(), 1.4); // bias toward smaller sizes

Raises a random 0-1 value to a power to skew results toward 0, making small bubbles more common than big ones.

const t = pow(random(), 1.4); // bias toward smaller sizes
Generates a random number between 0 and 1, then raises it to the power 1.4, which pushes more values toward 0 - this becomes a 'size factor' used below.
this.radius = lerp(minR, maxR, t);
Uses lerp() to interpolate between the smallest and largest radius based on t, so most bubbles end up small and a few end up large.
this.x = width / 2 + random(-spread, spread);
Places the bubble horizontally near the center of the screen with some random spread to either side.
this.y = height + random(10, 80); // start just below bottom
Starts the bubble just below the bottom edge of the canvas so it appears to rise up into view.
this.vy = -lerp(0.7, 1.6, 1 - t); // smaller bubbles a bit slower
Sets the upward speed (negative because up is negative y in p5), making smaller bubbles rise slightly slower than bigger ones.
this.wobbleAmp = random(5, 18);
Picks how many pixels the bubble sways side to side as it rises.
this.lifespan = random(8000, 18000); // 8–18 seconds
Chooses how many milliseconds this bubble will exist before it's considered 'dead' and removed.
this.baseHue = random(0, 360);
Picks a random starting hue (0-360 degrees) that will be used to color this bubble's iridescent gradient.

update()

update() is called once per frame for every living bubble and is intentionally minimal - it just moves the bubble's stored x/y position, leaving all the visual complexity to display().

update() {
    this.y += this.vy;
    this.x += this.vxBase * 0.7; // gentle sideways drift
  }
Line-by-line explanation (2 lines)
this.y += this.vy;
Moves the bubble upward each frame by adding its (negative) vertical velocity to its y position.
this.x += this.vxBase * 0.7; // gentle sideways drift
Nudges the bubble sideways very slightly, scaled down to 70% of its base drift speed to keep the motion gentle.

getFadeFactor()

This function is a classic 'envelope' pattern used in animation and audio synthesis alike: combine a fade-in ramp and a fade-out ramp to smoothly bring something into existence and gently remove it, avoiding jarring pops.

🔬 fadeIn currently reaches full opacity after just 12% of a bubble's life. What happens if you change 0.12 to 0.5, making bubbles take half their lifetime to fully materialize?

    const fadeIn = constrain((t - 0.0) / 0.12, 0, 1);
    // Fade-out over last 35% of life
    const fadeOut = 1 - constrain((t - 0.65) / 0.35, 0, 1);
getFadeFactor() {
    const age = millis() - this.birth;
    const t = constrain(age / this.lifespan, 0, 1);

    // Fade-in over first 12% of life
    const fadeIn = constrain((t - 0.0) / 0.12, 0, 1);
    // Fade-out over last 35% of life
    const fadeOut = 1 - constrain((t - 0.65) / 0.35, 0, 1);

    return fadeIn * fadeOut;
  }
Line-by-line explanation (5 lines)

🔧 Subcomponents:

calculation Fade-In Ramp const fadeIn = constrain((t - 0.0) / 0.12, 0, 1);

Ramps opacity from 0 to 1 over the first 12% of the bubble's life.

calculation Fade-Out Ramp const fadeOut = 1 - constrain((t - 0.65) / 0.35, 0, 1);

Ramps opacity from 1 back down to 0 over the last 35% of the bubble's life.

const age = millis() - this.birth;
Calculates how many milliseconds have passed since this bubble was created.
const t = constrain(age / this.lifespan, 0, 1);
Converts age into a 0-to-1 progress value representing how far through its life the bubble is, clamped so it never goes below 0 or above 1.
const fadeIn = constrain((t - 0.0) / 0.12, 0, 1);
Creates a value that rises from 0 to 1 as t goes from 0 to 0.12 (the first 12% of life), then stays at 1.
const fadeOut = 1 - constrain((t - 0.65) / 0.35, 0, 1);
Creates a value that stays at 1 until t reaches 0.65, then falls from 1 to 0 as t goes from 0.65 to 1.0 (the last 35% of life).
return fadeIn * fadeOut;
Multiplies the two ramps together so the bubble fades in at birth, stays fully visible in the middle, and fades out before it pops.

isDead()

isDead() is a simple boolean check used by updateAndDrawBubbles() to decide when to splice a bubble out of the array - keeping the cleanup logic separate from the movement and drawing logic.

isDead() {
    const age = millis() - this.birth;
    // Pop when above the top or past lifespan
    return age > this.lifespan || this.y + this.radius < -40;
  }
Line-by-line explanation (2 lines)
const age = millis() - this.birth;
Recalculates how long this bubble has existed, in milliseconds.
return age > this.lifespan || this.y + this.radius < -40;
Returns true (meaning 'remove this bubble') if either its lifespan has expired OR it has drifted far enough above the top of the canvas that it's no longer visible.

display()

display() is where the sketch drops down to raw Canvas2D calls (drawingContext) to access radial gradients, a feature p5.js doesn't wrap directly. This is a powerful pattern: use p5 for the easy stuff (ellipse, arc, push/pop) and the native context for advanced effects like multi-stop gradients.

🔬 These are the first two color stops of the iridescent gradient. What happens if you swap the 95%, 85% saturation/lightness values for something like 40%, 20%, making the sheen look darker and more metallic?

    grad.addColorStop(0.0, `rgba(255,255,255,${0.9 * alphaScale})`);                      // bright core
    grad.addColorStop(0.25, `hsla(${hue}, 95%, 85%, ${0.75 * alphaScale})`);              // soft highlight
display() {
    const ctx = drawingContext; // 2D CanvasRenderingContext2D
    const r = this.radius;
    const time = millis() / 1000;

    const fade = this.getFadeFactor();
    if (fade <= 0) return;

    const alphaScale = this.baseAlpha * fade;

    // Wobble: time-based horizontal oscillation
    const wobbleX = sin(TWO_PI * this.wobbleFreq * time + this.phase) * this.wobbleAmp;
    const x = this.x + wobbleX;
    const y = this.y;

    // Sheen highlight: small bright spot that orbits around the bubble
    const angle = this.sheenSpeed * time + this.phase;
    const sheenOffset = r * 0.4;
    const gx = x + cos(angle) * sheenOffset;
    const gy = y + sin(angle) * sheenOffset;

    // Iridescent radial gradient
    const grad = ctx.createRadialGradient(gx, gy, r * 0.15, x, y, r);

    // Animate the rainbow hue slightly over time
    const hue = (this.baseHue + time * 40) % 360;

    grad.addColorStop(0.0, `rgba(255,255,255,${0.9 * alphaScale})`);                      // bright core
    grad.addColorStop(0.25, `hsla(${hue}, 95%, 85%, ${0.75 * alphaScale})`);              // soft highlight
    grad.addColorStop(0.5, `hsla(${(hue + 60) % 360}, 90%, 70%, ${0.45 * alphaScale})`);  // warm tint
    grad.addColorStop(0.8, `hsla(${(hue + 140) % 360}, 90%, 65%, ${0.20 * alphaScale})`); // cool rim
    grad.addColorStop(1.0, `rgba(255,255,255,0)`);                                        // fade to transparent

    // Use the gradient as fill for the bubble
    ctx.fillStyle = grad;

    noStroke();
    ellipse(x, y, r * 2, r * 2);

    // Subtle outer rim to make the bubble edge visible
    stroke(255, 255, 255, 160 * alphaScale);
    strokeWeight(r * 0.06);
    noFill();
    ellipse(x, y, r * 2.02, r * 2.02);

    // Small inner highlight arc for extra realism
    push();
    translate(x, y);
    rotate(-PI / 3);
    stroke(255, 255, 255, 200 * alphaScale);
    strokeWeight(r * 0.07);
    noFill();
    arc(0, 0, r * 1.1, r * 1.1, -PI * 0.15, PI * 0.35);
    pop();
  }
Line-by-line explanation (12 lines)

🔧 Subcomponents:

conditional Invisible Bubble Skip if (fade <= 0) return;

Skips all the expensive drawing work if the bubble is fully faded out, saving performance.

calculation Iridescent Gradient Stops grad.addColorStop(0.25, `hsla(${hue}, 95%, 85%, ${0.75 * alphaScale})`);

Layers five color stops from white core to transparent rim to build the rainbow sheen.

const ctx = drawingContext; // 2D CanvasRenderingContext2D
Grabs p5's underlying HTML5 Canvas 2D context so we can use native canvas features like gradients that p5 doesn't expose directly.
const fade = this.getFadeFactor();
Computes how opaque this bubble should be right now based on its age (fade-in/fade-out envelope).
if (fade <= 0) return;
Exits early and draws nothing if the bubble is completely invisible, avoiding wasted drawing calls.
const wobbleX = sin(TWO_PI * this.wobbleFreq * time + this.phase) * this.wobbleAmp;
Uses a sine wave based on elapsed time to calculate a smooth side-to-side wobble offset for this frame.
const angle = this.sheenSpeed * time + this.phase;
Calculates an angle that increases over time, used to slowly orbit the bright highlight spot around the bubble.
const grad = ctx.createRadialGradient(gx, gy, r * 0.15, x, y, r);
Creates a radial gradient that starts at the offset highlight point (gx, gy) with a small inner radius and spreads out to the bubble's edge at (x, y) with radius r.
const hue = (this.baseHue + time * 40) % 360;
Slowly shifts the bubble's base hue over time using the modulo operator to keep it wrapped within 0-360 degrees, creating a subtle shimmering color animation.
grad.addColorStop(0.0, `rgba(255,255,255,${0.9 * alphaScale})`); // bright core
Sets the very center of the gradient to a bright, mostly-opaque white for the highlight core.
ctx.fillStyle = grad;
Tells the canvas to use this gradient object as the fill color for the next shape drawn.
ellipse(x, y, r * 2, r * 2);
Draws the main bubble body as a circle using the gradient fill just assigned.
stroke(255, 255, 255, 160 * alphaScale);
Sets a semi-transparent white outline color to give the bubble a visible edge.
arc(0, 0, r * 1.1, r * 1.1, -PI * 0.15, PI * 0.35);
Draws a small curved highlight stroke (relative to the translated/rotated origin) to fake a glassy reflection on the bubble's surface.

drawBackgroundGradient()

This function is called first in every draw() call, so it doubles as the 'clear the screen' step and the atmospheric backdrop, all in one linear gradient fill using the native Canvas2D API.

function drawBackgroundGradient() {
  const ctx = drawingContext;
  const gradient = ctx.createLinearGradient(0, 0, 0, height);

  // Soft blue vertical gradient
  gradient.addColorStop(0.0, '#cbeaff');  // very light at top
  gradient.addColorStop(0.4, '#a3d6ff');  // soft sky blue
  gradient.addColorStop(1.0, '#3b6fb3');  // deeper blue at bottom

  ctx.fillStyle = gradient;
  ctx.fillRect(0, 0, width, height);
}
Line-by-line explanation (6 lines)
const gradient = ctx.createLinearGradient(0, 0, 0, height);
Creates a gradient that runs vertically from the top of the canvas (y=0) to the bottom (y=height).
gradient.addColorStop(0.0, '#cbeaff'); // very light at top
Sets the color at the very top of the gradient (0%) to a very pale blue.
gradient.addColorStop(0.4, '#a3d6ff'); // soft sky blue
Sets an intermediate color stop 40% of the way down to a soft sky blue, creating a smooth transition.
gradient.addColorStop(1.0, '#3b6fb3'); // deeper blue at bottom
Sets the color at the very bottom (100%) to a deeper, richer blue for depth.
ctx.fillStyle = gradient;
Tells the canvas context to use this gradient as its fill color for the next shape drawn.
ctx.fillRect(0, 0, width, height);
Fills the entire canvas with a rectangle painted using the gradient, effectively painting the whole background.

windowResized()

windowResized() is a p5.js callback that fires automatically on browser resize events, letting you keep full-window sketches responsive without any extra event-listener code.

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
}
Line-by-line explanation (1 lines)
resizeCanvas(windowWidth, windowHeight);
Automatically called by p5 whenever the browser window is resized; this resizes the canvas to match the new window dimensions so the sketch always fills the screen.

📦 Key Variables

bubbles array

Holds every currently-alive Bubble object; grows when new bubbles spawn and shrinks when they pop.

let bubbles = [];
spawnAccumulator number

Tracks fractional 'bubbles owed' over time so new bubbles spawn at a steady, frame-rate-independent rate.

let spawnAccumulator = 0;
BUBBLES_PER_SECOND number

Constant controlling the average number of new bubbles created per second.

const BUBBLES_PER_SECOND = 3.5;
MAX_BUBBLES number

Constant capping the maximum number of bubbles allowed on screen at once, to keep performance in check.

const MAX_BUBBLES = 80;

🔧 Potential Improvements (4)

Here are some ways this code could be enhanced:

PERFORMANCE Bubble.display()

A brand new radial gradient object is created from scratch every single frame for every bubble via ctx.createRadialGradient(), which is relatively expensive when dozens of bubbles are on screen.

💡 Consider caching gradients per bubble and only rebuilding them every few frames, or reducing the number of color stops, to lighten the per-frame workload.

BUG spawnBubbles()

If the browser tab is backgrounded and then refocused, deltaTime can spike to a huge value, causing spawnAccumulator to jump way above 1 and spawn a sudden burst of bubbles all at once.

💡 Clamp deltaTime (e.g. min(deltaTime, 100)) before using it in the accumulator calculation to avoid sudden bubble bursts after a lag spike.

STYLE Bubble constructor

Many magic numbers (16, 70, 0.7, 1.6, 0.15, 18, etc.) are scattered directly in the constructor, making it harder to tweak the look of bubbles consistently.

💡 Extract these into named top-level constants (e.g. MIN_RADIUS, MAX_RADIUS, MIN_RISE_SPEED) so they're easier to find, tune, and document.

FEATURE sketch-wide

The scene currently has no interactivity - bubbles spawn and drift regardless of what the user does.

💡 Add a mousePressed() or mouseMoved() handler that spawns extra bubbles at the cursor, or nudges nearby bubbles away from the mouse, to make the bubble bath feel interactive.

🔄 Code Flow

Code flow showing setup, draw, spawnbubbles, updateanddrawbubbles, bubble, update, getfadefactor, isdead, display, drawbackgroundgradient, windowresized

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

graph TD start[Start] --> setup[setup] setup --> draw[draw loop] draw --> drawbackgroundgradient[drawBackgroundGradient] click setup href "#fn-setup" click draw href "#fn-draw" click drawbackgroundgradient href "#fn-drawbackgroundgradient" draw --> spawnbubbles[spawnBubbles] click spawnbubbles href "#fn-spawnbubbles" spawnbubbles --> spawn-while-loop[Spawn Accumulator Loop] click spawn-while-loop href "#sub-spawn-while-loop" draw --> updateanddrawbubbles[updateAndDrawBubbles] click updateanddrawbubbles href "#fn-updateanddrawbubbles" updateanddrawbubbles --> backward-for-loop[Backward Bubble Loop] click backward-for-loop href "#sub-backward-for-loop" backward-for-loop --> death-check[Pop Check] click death-check href "#sub-death-check" death-check --> isdead[isDead] click isdead href "#fn-isdead" updateanddrawbubbles --> update[update] click update href "#fn-update" update --> bubble[bubble] click bubble href "#fn-bubble" bubble --> size-bias-calc[Size Bias Calculation] click size-bias-calc href "#sub-size-bias-calc" bubble --> getfadefactor[getFadeFactor] click getfadefactor href "#fn-getfadefactor" getfadefactor --> fade-in-calc[Fade-In Ramp] click fade-in-calc href "#sub-fade-in-calc" getfadefactor --> fade-out-calc[Fade-Out Ramp] click fade-out-calc href "#sub-fade-out-calc" bubble --> display[display] click display href "#fn-display" display --> fade-guard[Invisible Bubble Skip] click fade-guard href "#sub-fade-guard" display --> gradient-stops[Iridenscent Gradient Stops] click gradient-stops href "#sub-gradient-stops" draw --> windowresized[windowResized] click windowresized href "#fn-windowresized"

❓ Frequently Asked Questions

What visual experience does the Iridescent Soap Bubbles sketch provide?

This sketch creates a dreamy and calming atmosphere by generating floating soap bubbles with an iridescent rainbow sheen against a soft blue gradient background.

Is there any user interaction available in the Iridescent Soap Bubbles sketch?

The sketch is primarily a visual experience with no interactive elements; users can enjoy watching the continuous spawning and drifting of bubbles.

What creative coding techniques are demonstrated in the Iridescent Soap Bubbles sketch?

The sketch showcases the use of 2D canvas gradients, class-based object management for bubble creation, and physics-based motion to simulate the drifting and wobbling of bubbles.

Preview

Iridescent Soap Bubbles - xelsed.ai - p5.js creative coding sketch preview
Sketch Preview
Code flow diagram showing the structure of Iridescent Soap Bubbles - xelsed.ai - Code flow showing setup, draw, spawnbubbles, updateanddrawbubbles, bubble, update, getfadefactor, isdead, display, drawbackgroundgradient, windowresized
Code Flow Diagram