Skip to main content

Guide

canvas-confetti vs tsparticles vs party.js 2026

canvas-confetti vs tsparticles vs party.js compared for web celebration effects. Bundle size, particle systems, React integration, and performance in 2026.

·PkgPulse Team·
0

canvas-confetti vs tsparticles vs party.js: Celebration Effects 2026

TL;DR

Adding confetti, fireworks, particles, and celebration effects to web apps is a quick win for delight — form submission success, milestone achievements, game completions, onboarding. Three libraries dominate this space. canvas-confetti is the obvious default — zero dependencies, 6kB gzipped, renders beautifully on a canvas element, supports confetti from any position or the cannon pattern, and is used by GitHub, Linear, and thousands of production apps. tsparticles is the Swiss army knife of particle engines — fully configurable backgrounds (snow, bubbles, connecting dots, fireworks, confetti), complex particle systems with physics, plugins for ReactJS/Vue/Svelte, but it's heavier (~20-100kB depending on plugins) and more complex to configure. party.js takes a different approach — DOM-based effects triggered off any HTML element with a high-level declarative API; it supports confetti, sparkles, and ribbons without a canvas element, making it easy to integrate with existing UI. For a quick confetti celebration on a button click: canvas-confetti. For complex ambient particle backgrounds or full particle systems: tsparticles. For element-anchored effects without canvas setup: party.js.

Key Takeaways

  • canvas-confetti is ~6kB — smallest option, zero dependencies, just works
  • tsparticles is fully configurable — 50+ presets, physics engine, plugins ecosystem
  • party.js targets DOM elementsparty.confetti(button) anchors to any element
  • canvas-confetti renders on <canvas> — fires from screen position or element
  • tsparticles has React component<Particles> component with @tsparticles/react
  • All three support React — via hooks, components, or direct import
  • canvas-confetti can fire from multiple origins — simultaneous cannons pattern

Use Case Map

Button click celebration             → canvas-confetti
Page milestone (100 orders!)         → canvas-confetti or party.js
Ambient snow/particles background    → tsparticles
Fireworks display                    → tsparticles or canvas-confetti
Element-anchored sparkle effect      → party.js
Complex interactive particle system  → tsparticles
Simple confetti burst                → canvas-confetti
Ribbon/streamer effects              → party.js or canvas-confetti

canvas-confetti: The Default Choice

canvas-confetti is the most-used confetti library — battle-tested, tiny, and visually polished.

Installation

npm install canvas-confetti
# TypeScript types:
npm install @types/canvas-confetti

Basic Confetti

import confetti from "canvas-confetti";

// Simple burst from center
confetti();

// Custom position and options
confetti({
  particleCount: 150,
  spread: 70,
  origin: { y: 0.6 },
  colors: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"],
});

React Button with Confetti

import confetti from "canvas-confetti";
import type { Options } from "canvas-confetti";

interface ConfettiButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  children: React.ReactNode;
}

export function ConfettiButton({ children, onClick, ...props }: ConfettiButtonProps) {
  function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
    // Fire confetti from button position
    const rect = e.currentTarget.getBoundingClientRect();
    const x = (rect.left + rect.width / 2) / window.innerWidth;
    const y = (rect.top + rect.height / 2) / window.innerHeight;

    confetti({
      particleCount: 100,
      spread: 60,
      origin: { x, y },
      colors: ["#6366f1", "#8b5cf6", "#ec4899", "#06b6d4"],
      ticks: 200,
    });

    onClick?.(e);
  }

  return (
    <button onClick={handleClick} {...props}>
      {children}
    </button>
  );
}

Realistic Confetti from Sides

import confetti from "canvas-confetti";

function launchRealisticConfetti() {
  const duration = 5 * 1000;
  const end = Date.now() + duration;

  const colors = ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"];

  (function frame() {
    // From the left
    confetti({
      particleCount: 2,
      angle: 60,
      spread: 55,
      origin: { x: 0 },
      colors,
    });
    // From the right
    confetti({
      particleCount: 2,
      angle: 120,
      spread: 55,
      origin: { x: 1 },
      colors,
    });

    if (Date.now() < end) {
      requestAnimationFrame(frame);
    }
  })();
}

// Usage
document.getElementById("celebrate")?.addEventListener("click", launchRealisticConfetti);

School Pride / Firework Burst

import confetti from "canvas-confetti";

// School pride colors in a firework pattern
function fireConfettiBurst(x: number, y: number) {
  const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };

  const particleCount = 50;
  const colors = ["#003087", "#FFB81C"]; // Team colors

  confetti({
    ...defaults,
    particleCount,
    origin: { x, y: y - 0.1 },
    colors,
  });
  confetti({
    ...defaults,
    particleCount,
    origin: { x: x + 0.1, y },
    colors,
  });
}

// Random fireworks display
function startFireworks() {
  const duration = 3 * 1000;
  const animationEnd = Date.now() + duration;

  const interval = setInterval(() => {
    const timeLeft = animationEnd - Date.now();

    if (timeLeft <= 0) {
      clearInterval(interval);
      return;
    }

    fireConfettiBurst(Math.random(), Math.random() - 0.2);
  }, 250);
}

useConfetti React Hook

import confetti from "canvas-confetti";
import type { Options } from "canvas-confetti";
import { useCallback } from "react";

export function useConfetti() {
  const fire = useCallback((options?: Options) => {
    confetti({
      particleCount: 100,
      spread: 70,
      origin: { y: 0.6 },
      ...options,
    });
  }, []);

  const fireFromElement = useCallback(
    (element: HTMLElement, options?: Options) => {
      const rect = element.getBoundingClientRect();
      fire({
        origin: {
          x: (rect.left + rect.width / 2) / window.innerWidth,
          y: (rect.top + rect.height / 2) / window.innerHeight,
        },
        ...options,
      });
    },
    [fire]
  );

  return { fire, fireFromElement };
}

// Usage
export function OrderSuccess() {
  const { fire } = useConfetti();

  return (
    <button onClick={() => fire({ particleCount: 200 })}>
      Place Order 🎉
    </button>
  );
}

tsparticles: Full Particle System

tsparticles is the complete particle engine — complex interactive backgrounds, physics-based particles, fireworks, snow, and more.

Installation

npm install @tsparticles/react @tsparticles/slim
# For full features:
npm install @tsparticles/all

React Confetti Component

import { useEffect, useState } from "react";
import Particles, { initParticlesEngine } from "@tsparticles/react";
import { loadSlim } from "@tsparticles/slim";
import type { ISourceOptions } from "@tsparticles/engine";

export function ConfettiBackground() {
  const [init, setInit] = useState(false);

  useEffect(() => {
    initParticlesEngine(async (engine) => {
      await loadSlim(engine);
    }).then(() => setInit(true));
  }, []);

  const options: ISourceOptions = {
    fullScreen: { enable: true, zIndex: 9999 },
    particles: {
      number: { value: 0 },
      color: {
        value: ["#26ccff", "#a25afd", "#ff5e7e", "#88ff5a", "#fcff42"],
      },
      shape: {
        type: ["circle", "square", "triangle"],
      },
      opacity: {
        value: { min: 0, max: 1 },
        animation: {
          enable: true,
          speed: 0.5,
          startValue: "max",
          destroy: "min",
        },
      },
      size: {
        value: { min: 3, max: 7 },
      },
      links: { enable: false },
      life: {
        duration: { value: 5 },
        count: 1,
      },
      move: {
        enable: true,
        gravity: { enable: true, acceleration: 15 },
        speed: { min: 5, max: 20 },
        decay: 0.1,
        direction: "none",
        random: true,
        outModes: { default: "destroy" },
      },
      rotate: {
        value: { min: 0, max: 360 },
        direction: "random",
        animation: { enable: true, speed: 30 },
      },
    },
    emitters: {
      position: { x: 50, y: 30 },
      rate: { quantity: 20, delay: 0.1 },
      life: { duration: 0.5, count: 1 },
    },
  };

  if (!init) return null;

  return <Particles id="celebration-particles" options={options} />;
}

Snow Background

import Particles, { initParticlesEngine } from "@tsparticles/react";
import { loadSlim } from "@tsparticles/slim";
import type { ISourceOptions } from "@tsparticles/engine";
import { useEffect, useState } from "react";

const snowOptions: ISourceOptions = {
  background: { color: { value: "#0d1117" } },
  particles: {
    number: { value: 80, density: { enable: true } },
    color: { value: "#ffffff" },
    shape: { type: "circle" },
    opacity: { value: { min: 0.1, max: 0.5 } },
    size: { value: { min: 1, max: 5 } },
    move: {
      direction: "bottom",
      enable: true,
      random: false,
      speed: { min: 1, max: 3 },
      straight: false,
    },
    wobble: { enable: true, distance: 5, speed: 5 },
  },
  interactivity: {
    events: {
      onHover: { enable: true, mode: "repulse" },
    },
    modes: {
      repulse: { distance: 100, duration: 0.4 },
    },
  },
};

export function SnowBackground() {
  const [init, setInit] = useState(false);

  useEffect(() => {
    initParticlesEngine(loadSlim).then(() => setInit(true));
  }, []);

  if (!init) return null;

  return <Particles options={snowOptions} />;
}
const networkOptions: ISourceOptions = {
  background: { color: { value: "#0d1117" } },
  particles: {
    number: { value: 80, density: { enable: true } },
    color: { value: "#6366f1" },
    links: {
      enable: true,
      distance: 150,
      color: "#6366f1",
      opacity: 0.3,
      width: 1,
    },
    move: {
      enable: true,
      speed: 1,
      direction: "none",
      random: true,
      straight: false,
    },
    size: { value: { min: 1, max: 3 } },
    opacity: { value: { min: 0.3, max: 0.7 } },
  },
  interactivity: {
    events: {
      onHover: { enable: true, mode: "grab" },
      onClick: { enable: true, mode: "push" },
    },
    modes: {
      grab: { distance: 200 },
      push: { quantity: 4 },
    },
  },
};

party.js: DOM-Anchored Effects

party.js fires effects from DOM elements — simpler API, no canvas setup required.

Installation

npm install party-js

Confetti from Button

import party from "party-js";
import { useRef } from "react";

export function CelebrationButton() {
  const buttonRef = useRef<HTMLButtonElement>(null);

  function handleClick() {
    if (buttonRef.current) {
      // Confetti burst from the button
      party.confetti(buttonRef.current, {
        count: party.variation.range(40, 80),
        size: party.variation.range(0.8, 1.4),
        spread: party.variation.range(35, 45),
        speed: party.variation.range(300, 600),
      });
    }
  }

  return (
    <button ref={buttonRef} onClick={handleClick}>
      🎉 Celebrate
    </button>
  );
}

Sparkle Effect

import party from "party-js";

export function SparkleButton() {
  function handleHover(e: React.MouseEvent<HTMLButtonElement>) {
    party.sparkles(e.currentTarget, {
      count: party.variation.range(10, 20),
      size: party.variation.range(0.4, 0.8),
    });
  }

  return (
    <button onMouseEnter={handleHover}>
      ✨ Hover me
    </button>
  );
}

Custom Colors and Shapes

import party from "party-js";

function fireCustomConfetti(element: HTMLElement) {
  party.confetti(element, {
    count: 60,
    // Custom shapes
    shapes: ["square", "roundedSquare"],
    // Custom color palette
    color: [
      party.Color.fromHex("#6366f1"),
      party.Color.fromHex("#8b5cf6"),
      party.Color.fromHex("#ec4899"),
    ],
  });
}

Feature Comparison

Featurecanvas-confettitsparticlesparty.js
Bundle size~6kB~20-100kB~12kB
Zero dependenciesNoNo
React componentHook only<Particles>No (imperative)
Confetti✅ Best-in-class
Fireworks✅ (manual)✅ PresetNo
Snow/ambientNoNo
Particle networkNoNo
SparklesNo
Physics engineBasic gravity✅ FullBasic
InteractivityNo✅ Hover/clickNo
DOM anchoredPosition-basedNo✅ Element-native
Canvas required❌ DOM-based
TypeScript
npm weekly2.5M800k100k
GitHub stars9k7k4.5k

Performance Considerations and Frame Budget

Particle effects compete with your application's rendering budget — the JavaScript thread and GPU have a fixed frame budget, and adding a complex particle system can push you below 60fps on mobile devices. canvas-confetti is highly optimized: it uses a single canvas element with requestAnimationFrame, renders only active particles, and cleans up automatically when all particles have settled. The library's small size and focused scope mean it has minimal impact on React's render cycle. tsparticles, particularly with the full @tsparticles/all package and physics-enabled particles, can consume significant GPU resources for complex background effects. The recommended approach for ambient particle backgrounds in production is to respect prefers-reduced-motion: detect the user's motion preference and either disable the particle system entirely or reduce it to a static visual. party.js creates DOM elements rather than canvas particles, which has the advantage of compositing naturally with existing z-index stacking but the disadvantage of layout thrashing if many particles are created simultaneously.

React Rendering Integration and SSR Compatibility

All three libraries need careful handling in server-rendered React applications (Next.js App Router, Remix) because they access browser globals. canvas-confetti and party.js access document and window at import time in some configurations — importing them directly in a server component causes build errors. The safe pattern for all three is dynamic import with { ssr: false } in Next.js or conditional execution inside useEffect. For canvas-confetti specifically: const confetti = (await import('canvas-confetti')).default inside a useCallback or useEffect is the correct pattern for App Router compatibility. tsparticles's initParticlesEngine must also run client-side — the recommended pattern in the documentation correctly uses useEffect for initialization. None of these libraries expose React Server Component-compatible APIs, which is expected given their inherently visual and DOM-dependent nature.

Accessibility and Reduced Motion Support

Celebration effects are decorative by nature, which means they should be suppressed for users who have enabled reduced motion preferences in their operating system. The prefers-reduced-motion media query is the standard mechanism. For canvas-confetti, check this before firing: if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) { confetti(...) }. tsparticles supports this via the reducedMotion option in the particles configuration — setting this causes the library to disable animations automatically for affected users. party.js requires manual prefers-reduced-motion checking before calling party.confetti(). Accessibility auditors increasingly flag animated effects as violations when they lack motion reduction support; adding this check is a low-effort, high-impact accessibility improvement. For users who rely on reduced motion (vestibular disorders, epilepsy risk), unexpected confetti explosions can be genuinely distressing, making this check more than a box-ticking exercise.

Bundle Size and Lazy Loading Strategies

For applications where celebration effects appear only occasionally (order success page, achievement unlock), loading the particle library eagerly in the main bundle wastes bandwidth for users who never trigger the effect. All three libraries support lazy loading cleanly. A common pattern is to trigger the import only when the effect is needed: a custom hook that dynamically imports canvas-confetti and fires it on demand adds zero to the initial bundle. tsparticles is the most suitable for this pattern since its modular structure lets you import only the slim preset rather than the full engine. For Next.js specifically, the next/dynamic function with ssr: false handles both the SSR issue and lazy loading in one declaration. The bundle size difference between tsparticles slim (~20KB) and all (~100KB) is significant enough that if you're using only confetti effects, the slim package is always preferable to the full bundle.

Community Adoption and Ecosystem Context

canvas-confetti's 9k GitHub stars and 2.5M weekly downloads reflect its position as the default choice — it appears in countless "how to add confetti" tutorials and has broad adoption in production applications at companies including GitHub for PR celebrations and Linear for achievement milestones. tsparticles has displaced the original particles.js library (which became unmaintained) and is the recommended migration path for teams using that library. party.js has a smaller community (4.5k stars) but fills a genuine niche for element-anchored effects that neither canvas-confetti nor tsparticles handles as elegantly. For long-term maintenance risk assessment: canvas-confetti is a simple, stable library unlikely to have breaking changes; tsparticles undergoes regular major version updates as the plugin architecture evolves; party.js has slowed in update frequency. All three are small enough that pinning to a specific version and auditing changes on upgrade is a reasonable low-overhead strategy.

When to Use Each

Choose canvas-confetti if:

  • Simple confetti burst on button click or form success
  • Want the smallest possible bundle (6kB zero deps)
  • Standard confetti pattern — cannon from bottom, burst from element
  • Already using canvas in the page
  • Need the most battle-tested, production-proven option

Choose tsparticles if:

  • Ambient background effect (snow, network, bubbles) for the whole page
  • Complex particle system with physics and interactivity
  • Fireworks or elaborate celebration sequences
  • Need a React component with declarative configuration
  • Marketing/landing page with particle effect as a design element

Choose party.js if:

  • Want effects anchored directly to DOM elements (fires from the button)
  • Simpler API — less configuration than tsparticles
  • Sparkle effect on hover for interactive UI elements
  • Don't want to manage canvas positioning manually
  • Mix of confetti + sparkle effects in the same page

Methodology

Data sourced from canvas-confetti documentation (github.com/catdad/canvas-confetti), tsparticles documentation (particles.js.org), party.js documentation (party.js.org), npm weekly download statistics as of February 2026, GitHub star counts as of February 2026, and bundle size measurements from bundlephobia.com.


Related: Framer Motion vs Motion One vs AutoAnimate for general animation libraries, or Lottie vs Rive vs CSS Animations for pre-designed animation playback.

See also: React vs Vue and React vs Svelte

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.