function setup() {
// Create a canvas that fills the entire browser window
createCanvas(windowWidth, windowHeight);
// Set the background to black for a night sky effect
background(0);
// Initialize gravity vector (downwards)
gravity = createVector(0, 0.2); // Adjust 0.2 for stronger/weaker gravity
// Set stroke weight for drawing points
strokeWeight(4);
// Set color mode to HSB for easier generation of bright, random colors
// Hue: 0-255 (full color spectrum)
// Saturation: 0-255 (0=grayscale, 255=vibrant)
// Brightness: 0-255 (0=black, 255=white)
// Alpha: 0-255 (0=transparent, 255=opaque)
colorMode(HSB, 255);
}
Line-by-line explanation (5 lines)
createCanvas(windowWidth, windowHeight);
- Creates a canvas that exactly matches the browser window's current width and height, so the fireworks fill the whole screen.
background(0);
- Paints the canvas solid black once at startup to set the night-sky scene before any drawing happens.
gravity = createVector(0, 0.2); // Adjust 0.2 for stronger/weaker gravity
- Creates a p5.Vector pointing straight down with a small magnitude - this single vector is added to every particle's acceleration each frame to simulate gravity.
strokeWeight(4);
- Sets the default line/point thickness to 4 pixels, used for drawing the rising rocket's bright point.
colorMode(HSB, 255);
- Switches p5's color system from default RGB to Hue-Saturation-Brightness with a 0-255 range, making it trivial to generate random vivid colors by just randomizing hue while keeping saturation and brightness maxed out.
constructor(targetX, targetY) {
// Starting position at the bottom of the canvas, random X
this.pos = createVector(random(width), height);
// Target position (where the mouse was clicked)
this.target = createVector(targetX, targetY);
// Velocity vector calculated to reach the target from the starting position
this.vel = p5.Vector.sub(this.target, this.pos);
// Set the magnitude (speed) of the firework's ascent
this.vel.setMag(random(8, 12)); // Adjust 8, 12 for different firework speeds
// Acceleration vector (initially 0, gravity will be applied to particles)
this.acc = createVector(0, 0);
// Flag to check if the firework has exploded
this.exploded = false;
// Array to hold particles after explosion
this.particles = [];
// Random hue for this firework and its particles
this.hu = random(255);
}
Line-by-line explanation (7 lines)
this.pos = createVector(random(width), height);
- Places the rocket's starting position at a random x along the very bottom edge of the canvas (y = height).
this.target = createVector(targetX, targetY);
- Stores the click position as the point this rocket is aiming for.
this.vel = p5.Vector.sub(this.target, this.pos);
- Subtracting the starting position from the target gives a vector pointing directly from the rocket toward its destination - this becomes the initial velocity direction.
this.vel.setMag(random(8, 12)); // Adjust 8, 12 for different firework speeds
- setMag() keeps the direction of the vector but resets its length (speed) to a random value between 8 and 12, so every rocket travels at a slightly different pace.
this.acc = createVector(0, 0);
- Starts acceleration at zero; while ascending, no forces act on the rocket itself (gravity only applies after it explodes into particles).
this.exploded = false;
- A flag that tracks whether this firework has already burst - used to decide whether to update/draw it as a rocket or as a cloud of particles.
this.hu = random(255);
- Picks one random hue (0-255 in HSB mode) that will be shared by the rocket and all of its resulting particles, so each explosion is a single consistent color.
🔬 This checks the distance to the target every frame. What happens if you change 10 to 100 - do fireworks start exploding noticeably earlier, before they visually reach your click?
let d = p5.Vector.dist(this.pos, this.target);
// If the firework is close enough to its target, explode
if (d < 10) { // Adjust 10 for explosion threshold
this.explode();
this.exploded = true;
}
update() {
if (!this.exploded) {
// Apply acceleration (currently 0 for firework's ascent, gravity affects particles)
this.vel.add(this.acc);
// Move the firework
this.pos.add(this.vel);
// Reset acceleration for next frame
this.acc.mult(0);
// Calculate distance to target
let d = p5.Vector.dist(this.pos, this.target);
// If the firework is close enough to its target, explode
if (d < 10) { // Adjust 10 for explosion threshold
this.explode();
this.exploded = true;
}
} else {
// If exploded, update each particle
for (let i = this.particles.length - 1; i >= 0; i--) {
let particle = this.particles[i];
particle.update();
// Remove particles that have faded out
if (particle.done()) {
this.particles.splice(i, 1);
}
}
}
}
Line-by-line explanation (10 lines)
🔧 Subcomponents:
conditional
Pre-Explosion Movement
if (!this.exploded) {
While the rocket hasn't exploded, moves it toward its target and checks how close it has gotten.
conditional
Explosion Trigger
if (d < 10) { // Adjust 10 for explosion threshold
Triggers the explosion once the rocket is within 10 pixels of its target destination.
for-loop
Particle Update Loop
for (let i = this.particles.length - 1; i >= 0; i--) {
Updates every particle from this explosion and removes any that have fully faded, iterating backwards for safe removal.
if (!this.exploded) {
- Branches behavior: rockets that haven't exploded yet move toward their target; exploded ones instead manage their particle cloud.
this.vel.add(this.acc);
- Adds the current acceleration to velocity - currently acc is zero for the rocket, so velocity stays constant during ascent.
this.pos.add(this.vel);
- Moves the rocket forward by adding its velocity vector to its position - this is the core of vector-based motion.
this.acc.mult(0);
- Resets acceleration to zero so forces don't accumulate incorrectly between frames.
let d = p5.Vector.dist(this.pos, this.target);
- Calculates the straight-line distance between the rocket's current position and its target using p5's built-in vector distance function.
if (d < 10) { // Adjust 10 for explosion threshold
- Once the rocket gets within 10 pixels of the target, it's considered 'arrived' and should explode.
this.explode();
this.exploded = true;
- Calls explode() to generate the burst of particles, then flips the exploded flag so future frames treat this object as a particle cloud instead of a rocket.
for (let i = this.particles.length - 1; i >= 0; i--) {
- Loops backwards through this firework's particles so any that are removed with splice() don't disrupt the iteration.
particle.update();
- Applies gravity, moves, and fades each individual particle.
if (particle.done()) {
this.particles.splice(i, 1);
}
- Removes a particle from the array once its lifespan has run out, keeping the particles array from growing forever.
constructor(x, y, hu) {
// Particle's position (explosion point)
this.pos = createVector(x, y);
// Random initial velocity, spreading outward
this.vel = p5.Vector.random2D();
this.vel.mult(random(1, 8)); // Adjust 1, 8 for different particle spread speeds
// Acceleration vector (gravity will be applied here)
this.acc = createVector(0, 0);
// Lifespan for fading out (starts fully opaque)
this.lifespan = 255;
// Hue inherited from the firework
this.hu = hu;
}
Line-by-line explanation (5 lines)
this.pos = createVector(x, y);
- Starts every particle at the exact point where the firework exploded.
this.vel = p5.Vector.random2D();
- p5.Vector.random2D() returns a unit vector pointing in a completely random direction - this is what makes particles fly outward in every direction from the explosion point.
this.vel.mult(random(1, 8)); // Adjust 1, 8 for different particle spread speeds
- Scales that random direction by a random speed between 1 and 8, so particles fly outward at varying speeds, creating a natural-looking burst rather than a perfect ring.
this.lifespan = 255;
- Starts the particle fully opaque (255 matches the max alpha in this HSB 0-255 color range) - this value will count down each frame to fade the particle out.
this.hu = hu;
- Stores the hue passed in from the parent Firework, so every particle from one explosion shares the same color.
done() {
return this.lifespan < 0;
}
Line-by-line explanation (1 lines)
return this.lifespan < 0;
- Simply reports whether this particle's lifespan has counted down past zero - once true, the particle is fully faded and can be safely removed.
🔬 This is the exact moment gravity affects a particle. What happens visually if you multiply the gravity vector before adding it, e.g. this.acc.add(p5.Vector.mult(gravity, 3)) - do particles fall much faster?
// Apply gravity to acceleration
this.acc.add(gravity);
// Apply acceleration to velocity
this.vel.add(this.acc);
update() {
// Apply gravity to acceleration
this.acc.add(gravity);
// Apply acceleration to velocity
this.vel.add(this.acc);
// Move the particle
this.pos.add(this.vel);
// Reset acceleration for next frame
this.acc.mult(0);
// Decrease lifespan to fade the particle
this.lifespan -= 5; // Adjust 5 for faster/slower fade
}
Line-by-line explanation (5 lines)
this.acc.add(gravity);
- Adds the global gravity vector to this particle's acceleration every frame, constantly pulling it downward.
this.vel.add(this.acc);
- Applies the accumulated acceleration to velocity - this is what causes the particle's downward speed to keep increasing over time, simulating realistic falling.
this.pos.add(this.vel);
- Moves the particle by its current velocity, the same basic vector-motion pattern used everywhere in this sketch.
this.acc.mult(0);
- Resets acceleration to zero after it's been applied, so gravity has to be re-added fresh every frame instead of stacking up incorrectly.
this.lifespan -= 5; // Adjust 5 for faster/slower fade
- Counts down the particle's lifespan, which controls both when it's considered 'done' and how transparent it appears in show().
show() {
// Set stroke color with fading alpha
stroke(this.hu, 255, 255, this.lifespan);
strokeWeight(2); // Slightly thinner stroke for particles
point(this.pos.x, this.pos.y);
}
Line-by-line explanation (3 lines)
stroke(this.hu, 255, 255, this.lifespan);
- Sets the drawing color using the particle's hue at full saturation and brightness, but with alpha equal to its current lifespan - as lifespan counts down, the particle visually fades to transparent.
strokeWeight(2); // Slightly thinner stroke for particles
- Makes particle points thinner than the rocket's point (which uses the global strokeWeight of 4), so exploded particles look like a fine spray rather than chunky dots.
point(this.pos.x, this.pos.y);
- Draws the particle as a single point at its current position, using the color and stroke weight just set.