fruitlearn / js /particles.js
Nguyen Van Manh
a
4cfb14f
Raw
History Blame Contribute Delete
1.81 kB
// Confetti burst on the celebration canvas. Runs its own animation loop; call `burst(x, y)`
// (stage-relative pixels) on a correct catch. Pointer-events are disabled in CSS so it never
// intercepts the game.
export function createConfetti(canvas) {
const ctx = canvas.getContext("2d");
let particles = [];
const colors = ["#ff595e", "#ffca3a", "#8ac926", "#1982c4", "#6a4c93", "#ff924c", "#ff6392"];
function resize() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
resize();
window.addEventListener("resize", resize);
function burst(x, y, count = 90) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 3 + Math.random() * 7;
particles.push({
x,
y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 5, // bias upward
size: 7 + Math.random() * 9,
color: colors[Math.floor(Math.random() * colors.length)],
rot: Math.random() * Math.PI,
vr: (Math.random() - 0.5) * 0.5,
life: 1,
});
}
}
function tick() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (particles.length) {
particles = particles.filter((p) => p.life > 0 && p.y < canvas.height + 40);
for (const p of particles) {
p.vy += 0.28; // gravity
p.vx *= 0.99; // drag
p.x += p.vx;
p.y += p.vy;
p.rot += p.vr;
p.life -= 0.012;
ctx.save();
ctx.globalAlpha = Math.max(0, p.life);
ctx.translate(p.x, p.y);
ctx.rotate(p.rot);
ctx.fillStyle = p.color;
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);
ctx.restore();
}
}
requestAnimationFrame(tick);
}
tick();
return { burst };
}