function setup() {
// Create a canvas that fills the entire window
createCanvas(windowWidth, windowHeight);
// Initialize gradient colors
gradientColor1 = color(224, 255, 255); // Azure (very light blue)
gradientColor2 = color(173, 216, 230); // Light Blue
// Generate initial bubbles
for (let i = 0; i < 15; i++) {
bubbles.push(new Bubble());
}
}
π§ Subcomponents:
function-call
Canvas Setup
createCanvas(windowWidth, windowHeight);
Creates a canvas that fills the entire browser window
variable-assignment
Gradient Color Initialization
gradientColor1 = color(224, 255, 255); gradientColor2 = color(173, 216, 230);
Sets the top and bottom colors for the background gradient
for-loop
Initial Bubble Generation
for (let i = 0; i < 15; i++) { bubbles.push(new Bubble()); }
Creates 15 bubble objects and adds them to the bubbles array
Line by Line:
createCanvas(windowWidth, windowHeight);
- Creates a canvas that matches the full width and height of the browser window, making the sketch responsive
gradientColor1 = color(224, 255, 255);
- Sets the top gradient color to a light azure blue (RGB: 224, 255, 255)
gradientColor2 = color(173, 216, 230);
- Sets the bottom gradient color to a slightly darker light blue (RGB: 173, 216, 230)
for (let i = 0; i < 15; i++) {
- Loops 15 times to create the initial set of bubbles
bubbles.push(new Bubble());
- Creates a new Bubble object and adds it to the bubbles array
function draw() {
// Draw the background gradient
drawGradientBackground();
// Update and display bubbles
for (let i = bubbles.length - 1; i >= 0; i--) {
let bubble = bubbles[i];
bubble.update();
bubble.display();
// If bubble has popped, create burst particles and remove it
if (bubble.isPopped()) {
// Create burst particles
for (let j = 0; j < 10; j++) {
burstParticles.push(new BurstParticle(bubble.x, bubble.y, bubble.color));
}
bubbles.splice(i, 1); // Remove the popped bubble
}
}
// Update and display burst particles
for (let i = burstParticles.length - 1; i >= 0; i--) {
let particle = burstParticles[i];
particle.update();
particle.display();
// Remove dead particles
if (particle.isDead()) {
burstParticles.splice(i, 1);
}
}
// Periodically add new bubbles
if (frameCount % 60 === 0) { // Add a new bubble every 60 frames (approx 1 second)
bubbles.push(new Bubble());
}
}
π§ Subcomponents:
function-call
Background Gradient Drawing
drawGradientBackground();
Draws the gradient background each frame
for-loop
Bubble Update and Display Loop
for (let i = bubbles.length - 1; i >= 0; i--) { ... }
Iterates through all bubbles backwards, updates their positions, displays them, and removes popped ones
conditional
Bubble Pop Detection
if (bubble.isPopped()) { ... }
Checks if a bubble has reached the top and popped, then creates burst particles
for-loop
Burst Particle Update and Display Loop
for (let i = burstParticles.length - 1; i >= 0; i--) { ... }
Updates and displays all burst particles, removing dead ones
conditional
New Bubble Spawning
if (frameCount % 60 === 0) { bubbles.push(new Bubble()); }
Adds a new bubble every 60 frames to maintain a continuous stream
Line by Line:
drawGradientBackground();
- Calls the function that draws the gradient background for this frame
for (let i = bubbles.length - 1; i >= 0; i--) {
- Loops through the bubbles array backwards (from last to first). This is important because we'll be removing items from the array during the loop
let bubble = bubbles[i];
- Gets the current bubble object from the array
bubble.update();
- Calls the bubble's update method to move it and update its animation properties
bubble.display();
- Calls the bubble's display method to draw it on the canvas
if (bubble.isPopped()) {
- Checks if the bubble has reached the top of the screen and should pop
for (let j = 0; j < 10; j++) { burstParticles.push(new BurstParticle(bubble.x, bubble.y, bubble.color)); }
- Creates 10 burst particles at the bubble's position with its color and adds them to the burstParticles array
bubbles.splice(i, 1);
- Removes the popped bubble from the bubbles array (splice removes 1 item at index i)
for (let i = burstParticles.length - 1; i >= 0; i--) {
- Loops through burst particles backwards for the same reason as bubbles (safe removal during iteration)
particle.update();
- Updates the particle's position and decreases its lifespan
particle.display();
- Draws the particle with fading transparency based on its remaining life
if (particle.isDead()) { burstParticles.splice(i, 1); }
- Removes the particle from the array once its lifespan reaches zero
if (frameCount % 60 === 0) {
- Checks if the current frame number is divisible by 60 (every 60 frames, approximately every 1 second at 60 fps)
bubbles.push(new Bubble());
- Creates a new bubble and adds it to the bubbles array
function drawGradientBackground() {
noFill();
for (let y = 0; y <= height; y++) {
// Interpolate colors based on y position
let inter = map(y, 0, height, 0, 1);
let c = lerpColor(gradientColor1, gradientColor2, inter);
stroke(c);
line(0, y, width, y);
}
}
π§ Subcomponents:
for-loop
Horizontal Line Loop
for (let y = 0; y <= height; y++) { ... }
Draws horizontal lines across the entire canvas height, each with an interpolated color
calculation
Color Interpolation
let inter = map(y, 0, height, 0, 1); let c = lerpColor(gradientColor1, gradientColor2, inter);
Calculates a blend between the two gradient colors based on vertical position
Line by Line:
noFill();
- Disables fill so only the stroke (line color) will be visible
for (let y = 0; y <= height; y++) {
- Loops from y=0 (top) to y=height (bottom), drawing one line per pixel vertically
let inter = map(y, 0, height, 0, 1);
- Converts the y position (0 to height) into a value between 0 and 1, where 0 is the top and 1 is the bottom
let c = lerpColor(gradientColor1, gradientColor2, inter);
- Blends between gradientColor1 and gradientColor2 based on the inter value, creating a smooth color transition
stroke(c);
- Sets the line color to the interpolated color
line(0, y, width, y);
- Draws a horizontal line from the left edge to the right edge at the current y position
class Bubble {
constructor() {
this.radius = random(20, 60); // Random size
this.x = random(this.radius, width - this.radius); // Random horizontal position
this.y = height + this.radius; // Start from bottom of screen
this.speed = random(0.5, 2.5); // Random rising speed
this.color = random(pastelColors); // Random pastel color
this.alpha = 180; // Translucency (0-255)
// Perlin noise offsets for wobble and shine animation
this.wobbleOffset = random(1000);
this.shineOffset = random(2000);
this.popped = false; // Flag to check if bubble has popped
}
update() {
this.y -= this.speed; // Move bubble up
// Wobble horizontally using Perlin noise
let wobbleMagnitude = this.radius * 0.2; // Wobble up to 20% of its radius
let wobbleSpeed = 0.02;
this.x += map(noise(this.wobbleOffset), 0, 1, -wobbleMagnitude, wobbleMagnitude);
this.wobbleOffset += wobbleSpeed;
// Update shine offset for animation
this.shineOffset += 0.03;
// Check if bubble has gone off the top of the screen
if (this.y < -this.radius) {
this.popped = true;
}
}
display() {
noStroke();
fill(this.color.r, this.color.g, this.color.b, this.alpha);
circle(this.x, this.y, this.radius * 2); // Draw the main bubble
// Draw subtle shine highlight
// Position the shine slightly offset from the center
let shineSizeFactor = 0.6; // Shine size relative to bubble radius
let shineOffsetFactor = 0.5; // Offset distance from bubble center
// Animate shine position slightly
let shineAngle = map(noise(this.shineOffset), 0, 1, 0, TWO_PI);
let shineX = this.x + cos(shineAngle) * shineOffsetFactor * this.radius;
let shineY = this.y + sin(shineAngle) * shineOffsetFactor * this.radius;
fill(255, 255, 255, 180); // White, semi-transparent for shine
ellipse(shineX, shineY, this.radius * shineSizeFactor, this.radius * shineSizeFactor * 0.6); // Elongated ellipse for shine
}
// Check if the bubble has popped
isPopped() {
return this.popped;
}
}
π§ Subcomponents:
function-call
Constructor Initialization
constructor() { ... }
Initializes all properties of a new bubble with random values
function-call
Update Method
update() { ... }
Updates bubble position, wobble, and checks if it should pop
calculation
Wobble Motion Using Perlin Noise
this.x += map(noise(this.wobbleOffset), 0, 1, -wobbleMagnitude, wobbleMagnitude);
Creates smooth, natural-looking side-to-side motion using Perlin noise
conditional
Pop Detection
if (this.y < -this.radius) { this.popped = true; }
Sets the popped flag when bubble goes off the top of the screen
function-call
Display Method
display() { ... }
Draws the bubble and its animated shine highlight
calculation
Animated Shine Highlight
let shineAngle = map(noise(this.shineOffset), 0, 1, 0, TWO_PI); let shineX = ...; let shineY = ...;
Animates a white highlight that moves around the bubble's surface
Line by Line:
this.radius = random(20, 60);
- Sets a random size for the bubble between 20 and 60 pixels
this.x = random(this.radius, width - this.radius);
- Positions the bubble randomly across the canvas width, keeping it within bounds
this.y = height + this.radius;
- Starts the bubble just below the bottom of the canvas so it rises into view
this.speed = random(0.5, 2.5);
- Each bubble rises at a different random speed between 0.5 and 2.5 pixels per frame
this.color = random(pastelColors);
- Randomly selects one of the four pastel colors from the pastelColors array
this.alpha = 180;
- Sets the bubble's transparency to 180 (out of 255), making it semi-transparent
this.wobbleOffset = random(1000);
- Initializes a random starting point for the Perlin noise wobble effect
this.shineOffset = random(2000);
- Initializes a random starting point for the shine animation
this.popped = false;
- Sets the initial state to not popped
this.y -= this.speed;
- Moves the bubble up by subtracting its speed from its y position
let wobbleMagnitude = this.radius * 0.2;
- Calculates how far the bubble can wobble (20% of its radius)
this.x += map(noise(this.wobbleOffset), 0, 1, -wobbleMagnitude, wobbleMagnitude);
- Uses Perlin noise to create smooth side-to-side movement, mapped to the wobble magnitude
this.wobbleOffset += wobbleSpeed;
- Advances the noise offset to get the next noise value next frame, creating continuous motion
this.shineOffset += 0.03;
- Advances the shine animation offset
if (this.y < -this.radius) { this.popped = true; }
- When the bubble moves completely off the top of the screen, mark it as popped
circle(this.x, this.y, this.radius * 2);
- Draws the main bubble circle (radius * 2 is the diameter)
let shineAngle = map(noise(this.shineOffset), 0, 1, 0, TWO_PI);
- Uses Perlin noise to animate the angle around the bubble where the shine appears
let shineX = this.x + cos(shineAngle) * shineOffsetFactor * this.radius;
- Calculates the x position of the shine using trigonometry (cosine)
let shineY = this.y + sin(shineAngle) * shineOffsetFactor * this.radius;
- Calculates the y position of the shine using trigonometry (sine)
ellipse(shineX, shineY, this.radius * shineSizeFactor, this.radius * shineSizeFactor * 0.6);
- Draws an elongated white ellipse at the calculated shine position to create a glossy highlight
class BurstParticle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.radius = random(2, 8); // Initial size of particle
this.life = random(30, 60); // Lifespan in frames (approx 0.5 to 1 second)
// Random velocity to spread particles out
this.vx = random(-2, 2);
this.vy = random(-2, 2);
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--; // Decrease lifespan
}
display() {
noStroke();
// Fade particle as its life decreases
let particleAlpha = map(this.life, 0, 60, 0, 200);
fill(this.color.r, this.color.g, this.color.b, particleAlpha);
circle(this.x, this.y, this.radius * 2);
}
// Check if particle has died
isDead() {
return this.life <= 0;
}
}
π§ Subcomponents:
function-call
Constructor with Parameters
constructor(x, y, color) { ... }
Creates a particle at a specific position with a specific color
variable-assignment
Random Velocity Assignment
this.vx = random(-2, 2); this.vy = random(-2, 2);
Gives each particle a random direction and speed
function-call
Update Method
update() { ... }
Moves the particle and decreases its lifespan
function-call
Display Method with Fading
display() { ... }
Draws the particle with transparency that increases as it dies
calculation
Alpha Fade Calculation
let particleAlpha = map(this.life, 0, 60, 0, 200);
Maps remaining life to transparency value, creating a fade effect
Line by Line:
constructor(x, y, color) {
- Creates a new BurstParticle at position (x, y) with the given color
this.x = x;
- Stores the starting x position
this.y = y;
- Stores the starting y position
this.color = color;
- Stores the color object (which has r, g, b properties)
this.radius = random(2, 8);
- Each particle has a random size between 2 and 8 pixels
this.life = random(30, 60);
- Each particle lives for 30-60 frames (about 0.5-1 second at 60 fps)
this.vx = random(-2, 2);
- Random horizontal velocity between -2 and 2 pixels per frame
this.vy = random(-2, 2);
- Random vertical velocity between -2 and 2 pixels per frame
this.x += this.vx;
- Moves the particle horizontally by its velocity
this.y += this.vy;
- Moves the particle vertically by its velocity
this.life--;
- Decreases the particle's lifespan by 1 frame each update
let particleAlpha = map(this.life, 0, 60, 0, 200);
- Converts remaining life (0-60) into transparency (0-200), so particles fade as they die
fill(this.color.r, this.color.g, this.color.b, particleAlpha);
- Sets the fill color to the particle's color with the calculated alpha transparency
circle(this.x, this.y, this.radius * 2);
- Draws a small circle at the particle's current position
return this.life <= 0;
- Returns true if the particle's life has reached zero or below