About this snippet

Click and 40 confetti pieces fire out from the cursor, arc over, then fall away and fade. There is no library involved, just a canvas and a requestAnimationFrame loop.

Each piece gets a random angle and speed, converted into velocity with cos and sin. That gives an even burst in every direction, unlike randomizing the x and y velocities separately, which quietly biases the spray toward the corners. Every frame multiplies velocity by a friction value and adds gravity to the vertical component.

Spin is drawn by translating the canvas to the particle position, rotating, then filling a rectangle around that origin. Faded particles are shifted off the front of the array, which is safe because they were pushed in age order. The loop keeps running when nothing is on screen, so cancel it with cancelAnimationFrame if battery life matters.

Size
3.3 KB
Uses
HTML, CSS, JavaScript
Loads
Nothing external
Views
253
<div class="click-area" id="click-area">
  <canvas id="confetti-canvas"></canvas>
  <div class="pointer-text">
    <h2>Celebrate Success</h2>
    <p>Click anywhere on the screen to trigger an explosion.</p>
  </div>
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  margin: 0;
  background: #ffffff;
  overflow: hidden;
}

.click-area {
  position: relative;
  width: 100vw;
  height: 100vh;
  cursor: pointer;
  background: radial-gradient(circle at center, #f8fafc 0%, #e2e8f0 100%);
}

#confetti-canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 2;
  pointer-events: none;
}

.pointer-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  z-index: 1;
  pointer-events: none;
}

.pointer-text h2 {
  font-size: 3rem;
  color: #0f172a;
  margin: 0 0 10px 0;
  letter-spacing: -1px;
}

.pointer-text p {
  font-size: 1.25rem;
  color: #64748b;
  margin: 0;
}
const canvas = document.getElementById("confetti-canvas");
const ctx = canvas.getContext("2d");
const clickArea = document.getElementById("click-area");

let width = window.innerWidth;
let height = window.innerHeight;
canvas.width = width;
canvas.height = height;

window.addEventListener("resize", function() {
  width = window.innerWidth;
  height = window.innerHeight;
  canvas.width = width;
  canvas.height = height;
});

const particles = new Array();

function getConfettiColor() {
  const rand = Math.random();
  if (rand < 0.2) return "#3b82f6";
  if (rand < 0.4) return "#ec4899";
  if (rand < 0.6) return "#10b981";
  if (rand < 0.8) return "#f59e0b";
  return "#8b5cf6";
}

function spawnExplosion(x, y) {
  const count = 40;
  for (let i = 0; i < count; i++) {
    const p = new Object();
    p.x = x;
    p.y = y;
    p.color = getConfettiColor();
    p.size = Math.random() * 8 + 4;
    
    /* Random circular velocity */
    const angle = Math.random() * Math.PI * 2;
    const speed = Math.random() * 10 + 5;
    p.vx = Math.cos(angle) * speed;
    p.vy = Math.sin(angle) * speed;
    
    p.rotation = Math.random() * 360;
    p.rotSpeed = (Math.random() - 0.5) * 10;
    
    p.gravity = 0.3;
    p.friction = 0.96;
    p.opacity = 1;
    
    particles.push(p);
  }
}

clickArea.addEventListener("click", function(e) {
  spawnExplosion(e.clientX, e.clientY);
});

function animate() {
  ctx.clearRect(0, 0, width, height);
  
  for (let i = 0; i < particles.length; i++) {
    const p = particles.at(i);
    
    /* Apply physics */
    p.vx *= p.friction;
    p.vy *= p.friction;
    p.vy += p.gravity;
    
    p.x += p.vx;
    p.y += p.vy;
    p.rotation += p.rotSpeed;
    
    /* Fade out over time */
    p.opacity -= 0.01;
    
    if (p.opacity > 0) {
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate((p.rotation * Math.PI) / 180);
      ctx.fillStyle = p.color;
      ctx.globalAlpha = p.opacity;
      ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
      ctx.restore();
    }
  }
  
  /* Filter out dead particles natively without array bracket syntax */
  while (particles.length > 0 && particles.at(0).opacity <= 0) {
    particles.shift();
  }
  
  window.requestAnimationFrame(animate);
}

animate();
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save