We'll keep the code short and simple, so our fireworks will not look very fancy or realistic - in fact, they will be more similar to the ones you saw on Geocities twenty years ago. But it's still a good coding exercise.
The main program [lines 5-31] first declares the variables and then creates an array of the firework objects. Each firework will have its own x and y coordinates, age (time elapsed since the explosion), phase (fly/explode) and an array of sparks.
Each spark in turn has its own horizontal (vx) and vertical (vy) velocity, weight, which determines how quickly it falls and a color (determined by red, green and blue components).
In [31], we kick off the main animation function - explode [40-75].
This function updates and draws each spark of each firework.
[44-62] is the explosion phase. In this phase, each spark has a trail of 10 rectangles. We calculate the position of each rectangle [45-48] based on the coordinates of the firework, the velocity of the spark, the 'age' of the explosion and the index of the rectangle. I did not use any ballistic formulas, instead, I came up with this weirdo [48] by trial and error.
[49-52] calculates the color of each rectangle, taking into account the fade factor, which is derived from the 'age' of the explosion - the older the explosion, the darker the color.
In [60-61], there is a 5% chance of resetting the firework if the explosion is older than 100 frames.
The reset function [33-37] puts the firework back in a random place at the bottom of the screen and the 'fly' phase begins again.
In this phase [64-71], the firework moves up the screen [64] and an extremely rudimentary tail of sparks is drawn [65-69].
There is a .001 chance that the firework will explode (its phase changes) in a given frame [71]. It will explode for sure once the y coordinate reaches 200.
<html> |
<body style='background-color:black'> |
<canvas id='myCanvas' width='800' height='800'></canvas> |
<script> |
const max_fireworks = 5, |
max_sparks = 50; |
let canvas = document.getElementById('myCanvas'); |
let context = canvas.getContext('2d'); |
let fireworks = []; |
|
for (let i = 0; i < max_fireworks; i++) { |
let firework = { |
sparks: [] |
}; |
for (let n = 0; n < max_sparks; n++) { |
let spark = { |
vx: Math.random() * 5 + .5, |
vy: Math.random() * 5 + .5, |
weight: Math.random() * .3 + .03, |
red: Math.floor(Math.random() * 2), |
green: Math.floor(Math.random() * 2), |
blue: Math.floor(Math.random() * 2) |
}; |
if (Math.random() > .5) spark.vx = -spark.vx; |
if (Math.random() > .5) spark.vy = -spark.vy; |
firework.sparks.push(spark); |
} |
fireworks.push(firework); |
resetFirework(firework); |
} |
window.requestAnimationFrame(explode); |
|
function resetFirework(firework) { |
firework.x = Math.floor(Math.random() * canvas.width); |
firework.y = canvas.height; |
firework.age = 0; |
firework.phase = 'fly'; |
} |
|
function explode() { |
context.clearRect(0, 0, canvas.width, canvas.height); |
fireworks.forEach((firework,index) => { |
if (firework.phase == 'explode') { |
firework.sparks.forEach((spark) => { |
for (let i = 0; i < 10; i++) { |
let trailAge = firework.age + i; |
let x = firework.x + spark.vx * trailAge; |
let y = firework.y + spark.vy * trailAge + spark.weight * trailAge * spark.weight * trailAge; |
let fade = i * 20 - firework.age * 2; |
let r = Math.floor(spark.red * fade); |
let g = Math.floor(spark.green * fade); |
let b = Math.floor(spark.blue * fade); |
context.beginPath(); |
context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',1)'; |
context.rect(x, y, 4, 4); |
context.fill(); |
} |
}); |
firework.age++; |
if (firework.age > 100 && Math.random() < .05) { |
resetFirework(firework); |
} |
} else { |
firework.y = firework.y - 10; |
for (let spark = 0; spark < 15; spark++) { |
context.beginPath(); |
context.fillStyle = 'rgba(' + index * 50 + ',' + spark * 17 + ',0,1)'; |
context.rect(firework.x + Math.random() * spark - spark / 2, firework.y + spark * 4, 4, 4); |
context.fill(); |
} |
if (Math.random() < .001 || firework.y < 200) firework.phase = 'explode'; |
} |
}); |
window.requestAnimationFrame(explode); |
} |
</script> |
</body> |
</html> |