.
HTML5 Canvas · Click/Touch Launch · Rocket Trails · 80–140 Particles · Gravity & Friction · Glow shadowBlur · Heart Shape · Auto Ambient · No Libraries
The Coodeverse Fireworks Click Explosion Canvas 2025 is a free interactive HTML5 Canvas JavaScript demo where clicking or touching anywhere launches a rocket from the bottom of the screen that travels to the click point, leaving a fading trail array, then explodes into 80–140 colorful particles with realistic gravity (velocityY += 0.08) and friction (velocity *= 0.98), glow shadowBlur, and a 15% chance of a heart-shape burst using the parametric equation. A fading trail effect uses rgba(0,0,0,0.1) fillRect instead of clearRect. Auto ambient launches fire every 2.5 seconds. Pure vanilla HTML5 Canvas JavaScript — no libraries. 100% free.
Pure HTML5 Canvas + Vanilla JavaScript — no libraries. Teaches OOP class-based particle systems, gravity/friction physics, requestAnimationFrame animation loop, fading trail rgba technique, canvas shadowBlur glow, heart parametric equation, touch events, and array splice lifecycle management. One of the most comprehensive canvas tutorials available free.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Fireworks Click Explosion | Coodeverse</title>
<style>
body { margin:0; height:100vh; background:radial-gradient(circle at top,#001122,#000000); overflow:hidden; cursor:crosshair; user-select:none; }
h1 { position:absolute; top:20px; left:50%; transform:translateX(-50%); color:white; font-family:'Arial',sans-serif; font-size:42px; text-shadow:0 0 20px #00ffff; z-index:10; pointer-events:none; white-space:nowrap; }
#instructions { position:absolute; bottom:30px; left:50%; transform:translateX(-50%); color:#00ffff; font-family:'Courier New',monospace; font-size:20px; text-shadow:0 0 10px cyan; z-index:10; pointer-events:none; animation:pulse 3s infinite; white-space:nowrap; }
@keyframes pulse { 0%,100%{opacity:0.7;} 50%{opacity:1;} }
canvas { display:block; }
</style>
</head>
<body>
<h1>Click Anywhere = Fireworks!</h1>
<div id="instructions">Click or Tap to Launch Fireworks!</div>
<canvas id="canvas"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});
const colors = ['#ff0000','#00ff00','#0000ff','#ffff00','#ff00ff','#00ffff','#ff8800','#ff0066','#00ff99','#ffaa00','#ff33ff','#33ffff'];
const fireworks = [];
const heartParticles = [];
// ── Particle class: handles individual explosion particle physics ──
class Particle {
constructor(x, y, color, velocityX, velocityY) {
this.x = x; this.y = y;
this.color = color;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.alpha = 1;
this.friction = 0.98; // air resistance — multiply each frame
this.gravity = 0.08; // downward pull — add to velocityY each frame
this.size = Math.random() * 3 + 2;
}
update() {
this.velocityX *= this.friction; // slow horizontal
this.velocityY *= this.friction; // slow vertical
this.velocityY += this.gravity; // accelerate downward
this.x += this.velocityX;
this.y += this.velocityY;
this.alpha -= 0.008; // fade out
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = 20;
ctx.shadowColor = this.color;
ctx.fill();
ctx.restore();
}
}
// ── Firework class: manages rocket ascent + explosion lifecycle ──
class Firework {
constructor(x, y) {
this.x = x;
this.y = canvas.height; // start at bottom
this.targetY = y;
this.speed = Math.random() * 4 + 8;
this.color = colors[Math.floor(Math.random() * colors.length)];
this.trail = []; // array of past positions
this.exploded = false;
this.particles = [];
}
update() {
if (!this.exploded) {
this.y -= this.speed;
this.trail.push({ x: this.x, y: this.y });
if (this.trail.length > 15) this.trail.shift(); // limit trail
if (this.y <= this.targetY) this.explode();
}
this.particles.forEach((p, i) => {
p.update();
if (p.alpha <= 0) this.particles.splice(i, 1);
});
}
draw() {
if (!this.exploded) {
// Draw fading trail
this.trail.forEach((pt, i) => {
ctx.globalAlpha = i / this.trail.length; // fade older points
ctx.beginPath();
ctx.arc(pt.x, pt.y, 5, 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.shadowBlur = 20;
ctx.shadowColor = this.color;
ctx.fill();
});
ctx.globalAlpha = 1;
// Bright rocket head
ctx.beginPath();
ctx.arc(this.x, this.y, 6, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.shadowBlur = 30;
ctx.shadowColor = this.color;
ctx.fill();
}
this.particles.forEach(p => p.draw());
}
explode() {
this.exploded = true;
const count = Math.floor(Math.random() * 60) + 80; // 80-140 particles
for (let i = 0; i < count; i++) {
const angle = (Math.PI * 2 * i) / count;
const vel = Math.random() * 8 + 4;
const vx = Math.cos(angle) * vel;
const vy = Math.sin(angle) * vel;
const col = colors[Math.floor(Math.random() * colors.length)];
this.particles.push(new Particle(this.x, this.y, col, vx, vy));
}
// 15% chance of heart-shaped burst
if (Math.random() < 0.15) {
for (let t = 0; t < Math.PI * 2; t += 0.1) {
const hx = 16 * Math.pow(Math.sin(t), 3);
const hy = -(13 * Math.cos(t) + 5 * Math.cos(2*t) + 2 * Math.cos(3*t) + Math.cos(4*t));
const vx = hx * 0.1 + (Math.random() - 0.5) * 3;
const vy = hy * 0.1 + (Math.random() - 0.5) * 3;
heartParticles.push(new Particle(this.x + hx * 0.6, this.y + hy * 0.6, '#ff0066', vx, vy));
}
}
}
}
function launchFirework(x, y) {
fireworks.push(new Firework(x, y));
}
function animate() {
// Fading trail: rgba 0.1 alpha instead of clearRect
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
fireworks.forEach((fw, i) => {
fw.update(); fw.draw();
if (fw.exploded && fw.particles.length === 0) fireworks.splice(i, 1);
});
heartParticles.forEach((p, i) => {
p.update(); p.draw();
if (p.alpha <= 0) heartParticles.splice(i, 1);
});
requestAnimationFrame(animate);
}
// Click handler
canvas.addEventListener('click', e => {
const r = canvas.getBoundingClientRect();
launchFirework(e.clientX - r.left, e.clientY - r.top);
});
// Touch handler (mobile)
canvas.addEventListener('touchstart', e => {
e.preventDefault();
const r = canvas.getBoundingClientRect();
launchFirework(e.touches[0].clientX - r.left, e.touches[0].clientY - r.top);
});
// Auto ambient launches every 2.5 seconds, 60% fire probability
setInterval(() => {
if (Math.random() < 0.6) launchFirework(Math.random() * canvas.width, Math.random() * canvas.height * 0.5);
}, 2500);
animate();
</script>
</body>
</html>
this.y = canvas.height; this.targetY = clickY. Move up: this.y -= this.speed. When this.y <= targetY, call explode(). In explode(): const angle = (Math.PI * 2 * i) / count; vx = Math.cos(angle) * vel; vy = Math.sin(angle) * vel. Each particle: gravity velocityY += 0.08, friction velocity *= 0.98, fade alpha -= 0.008. Fading trail: ctx.fillStyle = 'rgba(0,0,0,0.1)'; ctx.fillRect(...) each frame.this.trail = []. Each frame push: this.trail.push({x:this.x,y:this.y}). Limit: if (this.trail.length > 15) this.trail.shift(). Draw with fade: trail.forEach((pt,i) => { ctx.globalAlpha = i / trail.length; ctx.arc(pt.x,pt.y,5,...); ctx.fill(); }). Reset: ctx.globalAlpha = 1. The index divided by length makes older points transparent — creating a natural comet tail.velocityX *= 0.98; velocityY *= 0.98 each frame — multiplying by 0.98 gradually slows particles. Gravity: velocityY += 0.08 each frame — adding a small positive value accelerates particles downward. Move: x += velocityX; y += velocityY. The combination creates realistic ballistic arcs identical to real fireworks. Lower friction (0.95) for faster decay; higher gravity (0.15) for heavier feel.for (let t = 0; t < Math.PI * 2; t += 0.1). Calculate: const hx = 16 * Math.pow(Math.sin(t), 3) and const hy = -(13*Math.cos(t) + 5*Math.cos(2*t) + 2*Math.cos(3*t) + Math.cos(4*t)). These give normalized coordinates -16 to 16. Use as velocities: vx = hx * 0.1; vy = hy * 0.1 so particles fly outward in heart shape. Add noise: (Math.random()-0.5)*3 for organic feel. Trigger at 15% probability: if (Math.random() < 0.15).ctx.clearRect(), use: ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; ctx.fillRect(0, 0, canvas.width, canvas.height). Alpha 0.1 darkens the canvas by 10% each frame — old drawings take ~10 frames to fully disappear, creating glowing comet trails. Lower alpha (0.05) = longer trails. Higher alpha (0.2) = shorter. For night sky fireworks, 0.1 is ideal.Rocket Trails · Particle Physics · Heart Shape · Glow shadowBlur · Touch Support · Auto Ambient · No Libraries · Free Forever
Trusted by 50,000+ developers worldwide · Coodeverse · Free Forever