canvas-confetti
Small browser-only confetti renderer that draws animated particles on a generated or supplied canvas. One function controls particle count, colors, shapes, launch angle, spread, velocity, gravity, drift, duration, origin, scale, and stacking. It returns a promise that settles when all current bursts finish, can render through a worker, and includes helpers for SVG-path and emoji shapes. It is a focused celebration effect, not a general particle engine or server-side image renderer.
A good small dependency for an occasional, nonessential browser celebration. Enable reduced-motion support, keep bursts bounded, and avoid worker mode on any canvas the rest of your application still owns.
Use it if
- You need a short celebratory browser effect with no framework dependency and a small measured payload
- You want to launch from a button, card, or screen coordinate and tune basic particle physics without building a canvas loop
- You will enable disableForReducedMotion and provide a non-motion success signal as the primary feedback
- You need custom emoji or single-color path shapes and can accept their browser and rendering constraints
- Your code must run in Node or during server rendering: the README explicitly says canvas-confetti is a client component and will not run in Node
- The effect communicates essential state: confetti is visual and temporary, and reduced-motion users may receive no animation at all when the accessibility option is enabled
- You need a full particle system with collisions, emitters, sprites, scenes, or long-running simulation: this API is intentionally limited to confetti bursts
- You need to keep drawing on the same canvas from the main thread while using worker mode: the README warns that useWorker transfers canvas control and further main-thread use throws
- You cannot guarantee modern Canvas APIs for custom paths: shapeFromPath depends on Path2D, fills rather than strokes, supports one color per path, and requires a transform matrix
Setup reality
npm install canvas-confetti adds no runtime dependencies and version 1.9.4 measured about 4.3 KB gzipped. The hidden constraint is environment: it touches browser canvas APIs and cannot run in Node, so SSR frameworks must import or call it only on the client after window and document exist. The default function creates and reuses a full-page canvas; repeated calls before completion share one promise and add particles to the same active animation. Accessibility is opt-in because disableForReducedMotion defaults to false. Set it to true globally or on every burst, then make sure the success state is also conveyed with text, focus, sound only where appropriate, or another non-motion cue. A custom canvas does not automatically match its CSS display size; enable resize or set its pixel dimensions yourself, and persist one custom confetti instance instead of recreating it for the same element. useWorker can reduce main-thread work, but it transfers the canvas to a worker and your code must stop reading or drawing it. Higher particleCount, ticks, large Path2D shapes, and repeated bursts still consume CPU and battery. shapeFromText rasterizes at creation time, so its scalar must match the burst scalar to avoid blur and web fonts must finish loading first. shapeFromPath matrix calculation is expensive and the README recommends computing and caching the matrix ahead of time, then regenerating it after library updates. reset stops animation and resolves outstanding promises rather than rejecting them, which can make awaiting code look like a normal completion.
Patterns
Launch an accessible default burstfire-basic-burst
import confetti from 'canvas-confetti';
await confetti({
particleCount: 80,
spread: 60,
disableForReducedMotion: true,
});The promise resolves immediately when reduced motion is requested, so essential success handling must not depend on seeing particles.
Launch from a button positionlaunch-from-element
const rect = button.getBoundingClientRect();
await confetti({
origin: {
x: (rect.left + rect.width / 2) / innerWidth,
y: (rect.top + rect.height / 2) / innerHeight,
},
disableForReducedMotion: true,
});origin uses page-relative fractions from 0 to 1, not CSS pixels. Recalculate after layout changes.
Create opposing burstsfire-from-both-sides
const common = { particleCount: 45, spread: 55, disableForReducedMotion: true };
await Promise.all([
confetti({ ...common, angle: 60, origin: { x: 0, y: 0.65 } }),
confetti({ ...common, angle: 120, origin: { x: 1, y: 0.65 } }),
]);Calls made during one active global animation return the same completion promise and share the generated canvas.
Use brand colors and starscustomize-palette
confetti({
particleCount: 100,
colors: ['#2563eb', '#7c3aed', '#f59e0b'],
shapes: ['star'],
scalar: 0.9,
disableForReducedMotion: true,
});Built-in shapes are square, circle, and star. Colors use CSS-style hexadecimal strings.
Limit particles to one canvascreate-custom-canvas
const canvas = document.querySelector('#celebration');
const celebrate = confetti.create(canvas, {
resize: true,
disableForReducedMotion: true,
});
celebrate({ particleCount: 80, spread: 90 });Create and persist one instance per canvas. resize lets the library change the canvas pixel dimensions as its display size changes.
Move a dedicated canvas to a workerrender-in-worker
const celebrate = confetti.create(canvas, {
resize: true,
useWorker: true,
disableForReducedMotion: true,
});
celebrate({ particleCount: 150, spread: 120 });After worker transfer, do not read from or draw on this canvas on the main thread. Removing it is still allowed.
Create a filled SVG path shapemake-path-shape
const triangle = confetti.shapeFromPath({
path: 'M0 10 L5 0 L10 10z',
});
confetti({
shapes: [triangle],
disableForReducedMotion: true,
});Path shapes require Path2D, are filled in one color, and matrix calculation should be cached for production.
Render emoji particles sharplymake-emoji-shape
const scalar = 2;
const rocket = confetti.shapeFromText({ text: '🚀', scalar });
confetti({
shapes: [rocket],
scalar,
disableForReducedMotion: true,
});Use the same scalar when creating and firing the rasterized text shape or the emoji can look blurry.
Create a text shape after font loadingwait-for-web-font
await document.fonts.load('700 24px Celebration');
const shape = confetti.shapeFromText({
text: 'YES',
fontFamily: 'Celebration',
color: '#7c3aed',
scalar: 2,
});shapeFromText rasterizes once. If the font is not loaded first, the fallback font is permanently captured in that shape.
Run a bounded animation looprun-timed-fireworks
const end = performance.now() + 2_000;
function frame(now) {
confetti({
particleCount: 4,
spread: 70,
origin: { x: Math.random(), y: 0.7 },
disableForReducedMotion: true,
});
if (now < end) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);Keep the duration and particles per frame low; repeated bursts share one canvas but still add rendering work.
Stop and clear current particlesstop-active-confetti
const done = confetti({ particleCount: 200 });
function cancelCelebration() {
confetti.reset();
}
await done;reset resolves outstanding promises immediately rather than rejecting them, so completion does not prove every particle finished naturally.
Import safely in an SSR applicationload-only-in-browser
async function celebrate() {
if (typeof window === 'undefined') return;
const { default: confetti } = await import('canvas-confetti');
await confetti({ disableForReducedMotion: true });
}The package is browser-only. Call this from a client event or effect, never during server rendering.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| js-confetti | npm | You want an equally focused confetti API with emoji support and prefer its class-based interface |
| party-js | npm | You need configurable emitters, templates, and particle effects beyond one confetti burst |
| @tsparticles/confetti | npm | You already use tsParticles or need a confetti preset inside its much larger particle ecosystem |