This page explains how to create a fireworks animation on a webpage using JavaScript and Canvas.
It includes copy‑and‑paste‑ready code that lets you implement a full‑featured fireworks effect, featuring radial particle bursts drawn with sin/cos, glowing gradients, and multi‑stage explosions.
Perfect for summer‑themed event pages or any seasonal visual effects.
HTML,Javascript
<canvas class="Hanabi" style="width:100%;max-width:800px;height:200px;"></canvas>
<script>
TMamHanabi = function(){
this.can = [];
this.ctx = [];
this.width = [];
this.height = [];
this.position = [];
this.basewh = [];
this.basesp = [];
this.limit = [];
this.flag = [];
this.child = [];
this.init = function(){
this.can = document.querySelectorAll(".Hanabi");
for(let i = 0; i < this.can.length; i++){
this.ctx[i] = this.can[i].getContext("2d", {willReadFrequently:true});
// Get the displayed size of the canvas
let style = window.getComputedStyle(this.can[i]);
this.width[i] = parseInt(style.width);
this.height[i] = parseInt(style.height);
this.basewh[i] = Math.min(this.width[i], this.height[i]);
this.basesp[i] = this.height[i] / 200;
// Set the internal canvas size
this.can[i].setAttribute("width", this.width[i] + "px");
this.can[i].setAttribute("height", this.height[i] + "px");
this.position[i] = [];
this.position[i]["x"] = this.width[i] / 2;
this.position[i]["y"] = this.height[i];
// Random explosion height
this.limit[i] = this.height[i] * 1/3 + Math.random() * this.height[i] / 3 - this.height[i] / 6;
this.flag[i] = 0;
this.child[i] = [];
this.reset(i);
// Fill background
this.ctx[i].fillStyle = "#000";
this.ctx[i].fillRect(0, 0, this.width[i], this.height[i]);
}
window.addEventListener("resize", this.resize.bind(this));
}
this.resize = function(){
for(let i = 0; i < this.can.length; i++){
// Get displayed size again on resize
let style = window.getComputedStyle(this.can[i]);
this.width[i] = parseInt(style.width);
this.height[i] = parseInt(style.height);
this.basewh[i] = Math.min(this.width[i], this.height[i]);
this.basesp[i] = this.height[i] / 200;
// Update internal canvas size
this.can[i].setAttribute("width", this.width[i] + "px");
this.can[i].setAttribute("height", this.height[i] + "px");
}
}
this.reset = function(i){
// Reset launch position and explosion height
this.position[i]["x"] = this.width[i] / 3 + this.width[i] * Math.random() / 3;
this.position[i]["y"] = this.height[i];
this.limit[i] = this.height[i] * 1/3 + Math.random() * this.height[i] / 3 - this.height[i] / 6;
this.flag[i] = 0;
// Generate child particles
let ct = 0;
for(let j = 0; j < 12; j++){
for(let k = 0; k < (j + 1) * 8; k++){
let deg = 360 / ((j + 1) * 8);
this.child[i][ct] = [];
this.child[i][ct]["x"] = this.position[i]["x"];
this.child[i][ct]["y"] = this.limit[i];
let rd = Math.random();
this.child[i][ct]["vx"] =
Math.cos(deg * k * Math.PI / 180) *
(j + 0.8 + rd * Math.random() * 0.4) *
this.basewh[i] / 640 / 5;
this.child[i][ct]["vy"] =
Math.sin(deg * k * Math.PI / 180) *
(j + 0.8 + rd * Math.random() * 0.4) *
this.basewh[i] / 640 / 5;
// Randomized color
let rr = Math.floor(Math.random() * j / 2 + 255 - j * 2);
let gg = Math.floor(Math.random() * j / 4 + 255 - j * 8);
let bb = Math.floor(Math.random() * j / 16 + 255 - j * 16);
this.child[i][ct]["c"] = '' + rr + ',' + gg + ',' + bb + '';
ct++;
}
}
}
this.draw = function(){
for(let i = 0; i < this.ctx.length; i++){
// Clear background
this.ctx[i].fillStyle = "#000";
this.ctx[i].fillRect(0, 0, this.width[i], this.height[i]);
// Launch phase
if(this.flag[i] == 0){
if(this.position[i]["y"] < this.limit[i]){
this.flag[i] = 1; // Switch to explosion phase
}else{
this.position[i]["y"] -= this.basesp[i];
}
// Draw rising light
let x = this.position[i]["x"];
let y = this.position[i]["y"];
let r = this.basewh[i] / 80 + Math.random() * this.basewh[i] / 80;
var rgrd = this.ctx[i].createRadialGradient(x, y, r, x, y, 0);
let rr = Math.floor(Math.random() * 5 + 250);
let gg = Math.floor(Math.random() * 55 + 200);
let bb = Math.floor(Math.random() * 55 + 200);
rgrd.addColorStop(0.0, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.0)');
rgrd.addColorStop(0.2, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.2)');
rgrd.addColorStop(1.0, 'rgba(' + rr + ',' + gg + ',' + bb + ',0.8)');
this.ctx[i].beginPath();
this.ctx[i].arc(x, y, r, 0, 2 * Math.PI);
this.ctx[i].fillStyle = rgrd;
this.ctx[i].fill();
// Explosion phase
}else if(this.flag[i] > 0){
if(this.flag[i] > 100){
this.flag[i] = -50;
return;
}
var r;
for(let j = 0; j < this.child[i].length; j++){
r = (1 + Math.random() / 2) * this.basewh[i] / 128;
// Gravity
this.child[i][j]["vy"] += this.basewh[i] / 160 / 200 * (1 + Math.random() / 8);
// Movement
this.child[i][j]["x"] += this.child[i][j]["vx"];
this.child[i][j]["y"] += this.child[i][j]["vy"];
// Particle glow
let rgrd = this.ctx[i].createRadialGradient(
this.child[i][j]["x"], this.child[i][j]["y"], r,
this.child[i][j]["x"], this.child[i][j]["y"], 0
);
let op = (100 - this.flag[i]) / 100;
rgrd.addColorStop(0.0, 'rgba(' + this.child[i][j]["c"] + ',' + 0.0 * op + ')');
rgrd.addColorStop(0.6, 'rgba(' + this.child[i][j]["c"] + ',' + 0.8 * op + ')');
rgrd.addColorStop(1.0, 'rgba(' + this.child[i][j]["c"] + ',' + 1.0 * op + ')');
this.ctx[i].beginPath();
this.ctx[i].arc(this.child[i][j]["x"], this.child[i][j]["y"], r, 0, 2 * Math.PI);
this.ctx[i].fillStyle = rgrd;
this.ctx[i].fill();
}
this.flag[i]++;
// Cooldown phase
}else{
this.flag[i]++;
if(this.flag[i] == 0){
this.reset(i);
}
}
}
}
window.addEventListener("DOMContentLoaded", this.init.bind(this));
setInterval(this.draw.bind(this), 20);
}
MamHanabi = new TMamHanabi();
</script>
