webdevpuneet.com

Confetti Celebration Card — Free HTML CSS JS Snippet

Confetti Celebration Card · Cards · Plain HTML, CSS & JS · Live preview

CategoryCards

Copy & paste

Source Code

<div class="demo">
  <canvas class="confetti-canvas"></canvas>
  <div class="card">
    <span class="badge">🎉 Milestone reached</span>
    <h3>1,000 subscribers!</h3>
    <p>You just crossed four figures — your audience is officially in the thousands. Keep the streak alive.</p>
    <button class="celebrate-btn" type="button">Celebrate</button>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Lightweight object-based particle system — confetti pieces are plain JavaScript objects with numeric physics fields, no animation library, particle-engine dependency, or class hierarchy required
Layered per-frame physics rules — independent gravity (vy += 0.12), spin (rotation += spin), and lifetime decay (life -= 0.012) accumulators stack into a convincingly organic burst-arc-tumble-fade motion
Rotation around each particle's own center — the save()/translate()/rotate()/restore() sequence with a -size/2 draw offset is the standard, directly-reusable recipe for spinning any shape correctly in Canvas
Self-starting, self-stopping animation loop — burst() only launches requestAnimationFrame if no loop is active, and tick() nulls the loop reference once every particle has expired, so the canvas never animates an empty scene
Randomized launch vectors — random angle and speed per particle make every burst look different and organic, never a repeating, mechanical-feeling spray
Tactile button-press feedback — a quick scale(0.97) transform-and-revert on click makes the celebration trigger feel physically pressed, reinforcing the moment alongside the visual burst
High-DPI canvas scaling and resize handling — devicePixelRatio scaling plus a resize listener keeps confetti crisp and correctly positioned across screen sizes and zoom levels

About this UI Snippet

How this confetti celebration card was built — a lightweight particle system with gravity, spin, and lifetime

Screenshot of the Confetti Celebration Card snippet rendered live

This snippet recreates the "you did it!" celebration moment found in goal-tracker apps, onboarding flows, and achievement screens — a glassy milestone card sits over a full-bleed canvas, and clicking "Celebrate" launches dozens of rotating, gravity-affected confetti rectangles that burst outward, tumble down, and fade away. It's a compact particle system — about 70 lines of JavaScript — built entirely on the Canvas 2D API with no animation or physics library.

Particles as plain objects with physics properties

Each piece of confetti is just a JavaScript object: a position (x, y), a velocity (vx, vy), a size, a color pulled from a five-colour palette, a rotation angle, a spin rate, and a life value that starts at 1 and counts down. burst() creates 90 of these on every click, each with a randomized launch angle (Math.random() * Math.PI * 2) and speed, so the shower fans out in every direction from a fixed origin point near the top of the card rather than looking like a uniform, mechanical spray. This "array of plain objects with simple numeric properties" approach is the simplest possible particle system — no classes, no inheritance, just numbers updated every frame.

Gravity, rotation, and fade — three small accumulators

The animation loop, tick(), applies three independent per-frame adjustments to every particle: p.vy += 0.12 constantly increases downward velocity (gravity), p.rotation += p.spin spins each piece at its own random rate, and p.life -= 0.012 counts down toward zero. Each of these is a single line of arithmetic, yet together they produce a shower that arcs outward, curves downward under gravity, tumbles convincingly as it falls, and gradually disappears — exactly the layered-simple-rules approach that makes procedural animation feel organic rather than scripted. The same "small independent per-frame adjustments stacking into complex motion" idea appears in the Audio Waveform Visualizer's layered sine waves, just applied to physics instead of geometry.

Drawing rotated rectangles around their own center

To make each confetti piece spin around its own midpoint rather than the canvas origin, tick() wraps each draw in ctx.save()/ctx.restore(), calls ctx.translate(p.x, p.y) to move the origin to the particle's position, then ctx.rotate(p.rotation), and finally draws a rectangle *offset by half its own size* (-p.size / 2, -p.size / 2) so it's centered on the new origin. This translate-rotate-draw-centered sequence is the standard recipe for rotating any shape around its own center in Canvas — directly reusable any time you need spinning sprites, gauges, or icons.

A self-starting, self-stopping animation loop

burst() only kicks off requestAnimationFrame(tick) if no loop is already running (if (!raf)), so rapid repeat clicks add more particles to the existing shower instead of starting parallel competing loops. tick() filters out particles whose life has reached zero or that have fallen below the canvas, and the moment the array is empty, it clears the canvas one final time and sets raf = null — stopping the animation loop entirely rather than looping forever on an empty scene. That null-check-and-restart structure is a clean, reusable pattern for any "fire and forget" canvas animation that shouldn't burn CPU once it's finished.

Step by step

How to Use

  1. 1
    Layer a full-bleed canvas behind a glass cardPosition a <canvas class="confetti-canvas"> with position: absolute; inset: 0; pointer-events: none behind a .card with backdrop-filter: blur() and position: relative; z-index: 1, so confetti appears to burst from behind the card.
  2. 2
    Model each particle as a plain object with physics fieldsOn burst, push objects like { x, y, vx, vy, size, color, rotation, spin, life: 1 } into a particles array — randomizing launch angle and speed with Math.random() so the shower fans out naturally rather than looking mechanical.
  3. 3
    Apply gravity, spin, and decay each frameIn the animation loop, run three small per-frame accumulators on every particle: p.vy += 0.12 (gravity), p.rotation += p.spin (tumbling), and p.life -= 0.012 (fade-out) — three single-line rules that combine into convincing physical motion.
  4. 4
    Draw each particle rotated around its own centerWrap each draw in ctx.save()/ctx.restore(), call ctx.translate(p.x, p.y) then ctx.rotate(p.rotation), and draw a rectangle offset by -size/2 in both axes — the standard translate-rotate-draw-centered recipe for spinning shapes in Canvas.
  5. 5
    Filter out dead particles and stop the loop when emptyAfter drawing, run particles = particles.filter(p => p.life > 0 && p.y < h + 40). If particles remain, re-queue requestAnimationFrame(tick); if the array is empty, clear the canvas once more and set the loop reference to null so it stops cleanly.
  6. 6
    Guard against overlapping burstsIn burst(), only start the animation loop if (!raf) — repeated clicks then simply add more particles to the array that the already-running loop is animating, instead of spawning duplicate competing loops.

Real-world uses

Common Use Cases

Achievement, milestone, and goal-completion screens
Celebrate a subscriber count, sales target, streak milestone, or completed goal with a physical, satisfying burst — turning a passive notification into a moment users want to screenshot and share.
Onboarding completion and first-success moments
Trigger the burst the instant a new user finishes setup, sends their first message, or completes their first task — positive reinforcement at exactly the moment it has the most impact on retention.
Gamified apps, habit trackers, and reward systems
Pair the celebration with Scratch Card Reveal for a two-step "scratch to reveal, then celebrate" sequence — or trigger it directly when a streak, level, or badge is earned.
Learning canvas particle systems and physics loops
A compact, fully-readable example of the array-of-objects particle pattern — the same foundational structure behind snow effects, fireworks, explosions, and game projectile systems, just scaled up.
Marketing pages and pricing-plan upgrade confirmations
Add a moment of delight right after a purchase, upgrade, or signup confirmation — the burst reinforces that something good just happened, building positive emotional association with the action taken.
A reference for rotating sprites around their own center
The translate-rotate-draw-offset sequence used for each confetti piece is the exact technique needed any time you draw a spinning gauge needle, rotating icon, or tumbling game sprite on canvas.
Related: GLB Spin-to-Reveal Stats Viewer
See the GLB Spin-to-Reveal Stats Viewer for a related cards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

Each of the 90 particles created in burst() gets a randomized launch angle via Math.random() * Math.PI * 2 (a full circle in radians) and a randomized speed between 2 and 7. Converting that angle and speed into vx/vy components with Math.cos/Math.sin makes every particle launch in a different direction at a different speed — so the shower fans outward organically, and no two bursts ever look quite the same.

Three tiny per-frame accumulators do all the work. p.vy += 0.12 continuously increases each particle's downward velocity, simulating gravity and producing the characteristic upward-arc-then-fall trajectory. p.rotation += p.spin spins each piece at its own random rate, creating convincing tumbling motion. p.life -= 0.012 counts down a fade-out value applied as ctx.globalAlpha. None of these alone looks like much, but layered together every frame, they combine into motion that reads as genuinely physical.

Canvas always rotates around the *current origin point* — by default, the canvas's top-left corner. To make a shape spin around its own center, you first move the origin to the shape's position with ctx.translate(p.x, p.y), then rotate with ctx.rotate(p.rotation), and finally draw the rectangle offset by half its own width and height (-p.size / 2, -p.size / 2) so it's centered on the new (rotated) origin. Wrapping this in ctx.save()/ctx.restore() ensures each particle's transform doesn't affect the next one's drawing.

So that clicking "Celebrate" repeatedly adds more confetti to an already-running shower instead of starting a second, competing requestAnimationFrame loop that would double-draw every frame and waste CPU. The raf variable holds the current loop's ID (or null when no loop is active); burst() only calls requestAnimationFrame(tick) when that variable is falsy, and tick() resets it to null once every particle has expired.

No — tick() filters the particles array down to only those whose life is still above zero and whose y position is still on screen (p.y < h + 40). The moment that array becomes empty, the loop performs one final clearRect(), sets raf = null, and simply does not re-queue itself — so the canvas goes idle and stops consuming any CPU until the next click triggers a fresh burst.

Yes — copy the HTML, CSS, and JS with the buttons on this page and use them anywhere, including commercial products, with no attribution required. It is built entirely on the native Canvas 2D API and vanilla JavaScript object/array operations — no particle-engine library, animation framework, or licensing to track.