GitHub - jonathanleane/partycles

11 min read Original article ↗

Partycles 🎉

A lightweight, performant React library for adding delightful animation effects to your applications. Perfect for celebrating user achievements, form submissions, or any moment worth highlighting.

→ Try the Live Demo

npm version Bundle Size TypeScript License: MIT

✨ Features

  • 🎯 Simple API - Just one hook to rule them all
  • 🎨 23 Beautiful Animations - Confetti, sparkles, hearts, stars, fireworks, bubbles, snow, emoji, coins, petals, aurora, fireflies, paint, balloons, galaxy, glitch, magicdust, crystals, leaves, mortar, bokeh, ribbons, and geometric
  • 📦 Tiny Bundle - Zero dependencies, optimized for performance
  • 🚀 Performant - Optimized animations using requestAnimationFrame
  • 🎮 Full Control - Customize colors, particle count, physics, and more
  • 📱 Mobile Optimized - Automatic performance optimizations for mobile devices
  • 🔧 TypeScript Support - Fully typed for excellent DX
  • React 18 Ready - Built with the latest React features

📦 Installation

npm install partycles
# or
yarn add partycles
# or
pnpm add partycles

🚀 Quick Start

import { useRef } from 'react';
import { useReward } from 'partycles';

function App() {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const { reward, isAnimating } = useReward(buttonRef, 'confetti');

  return (
    <button ref={buttonRef} onClick={reward} disabled={isAnimating}>
      Click me for a surprise! 🎉
    </button>
  );
}

That's it! No configuration needed - it just works. 🎊

Using String IDs (Legacy)

For backward compatibility, you can still use element IDs:

const { reward, isAnimating } = useReward('my-button', 'confetti');

<button id="my-button" onClick={reward}>
  Click me! 🎉
</button>

🤔 Why Partycles?

  • Zero Dependencies - No bloat, just pure React code
  • One Hook - Simple useReward hook handles everything
  • 23 Animations - From confetti to modern geometric bursts, we've got you covered
  • Fully Typed - Great TypeScript support out of the box
  • Customizable - Tweak colors, physics, particle count, and more
  • Performant - Optimized animations that won't slow down your app

📖 API Reference

useReward(targetRef, animationType, config?) (Recommended)

useReward(elementId, animationType, config?) (Legacy)

The main hook for creating reward animations.

Parameters

  • targetRef (RefObject): A React ref to the element to animate from (recommended)
  • elementId (string): The ID of the element to animate from (legacy, for backward compatibility)
  • animationType (string): One of: 'confetti', 'sparkles', 'hearts', 'stars', 'fireworks', 'bubbles', 'snow', 'emoji', 'coins', 'petals', 'aurora', 'fireflies', 'paint', 'balloons', 'galaxy', 'glitch', 'magicdust', 'crystals', 'leaves', 'mortar', 'bokeh', 'ribbons', 'geometric'
  • config (optional): Animation configuration object

Returns

  • reward (function): Triggers the animation, returns a Promise that resolves when complete
  • isAnimating (boolean): Whether the animation is currently running
  • pause (function): Pauses the current animation
  • resume (function): Resumes a paused animation
  • replay (function): Stops current animation and starts a new one
  • isPaused (boolean): Whether the animation is currently paused
  • targetRef (RefObject): The ref object (only returned when using ref-based API)

Configuration Options

interface AnimationConfig {
  particleCount?: number;      // Number of particles (default: varies by animation)
  spread?: number;             // Spread angle in degrees (default: 50)
  startVelocity?: number;      // Initial velocity (default: 45)
  decay?: number;              // Velocity decay rate (default: 0.9)
  lifetime?: number;           // Particle lifetime (default: 100)
  colors?: string[];           // Array of colors (or emojis for 'emoji' type)
  elementSize?: number;        // Size of particles in pixels
  duration?: number;           // Total animation duration
  physics?: {
    gravity?: number;          // Gravity force (default: 0.5)
    wind?: number;             // Horizontal wind force (default: 0)
    friction?: number;         // Air friction (default: 0.99)
  };
  // NEW: Optional enhanced effects
  effects?: {
    flutter?: boolean;         // Paper-like floating for confetti
    twinkle?: boolean;         // Brightness variation for stars/sparkles
    pulse?: boolean;           // Heartbeat effect for hearts
    spin3D?: boolean;          // 3D rotation for coins
    wobble?: boolean;          // Realistic wobble for bubbles
    windDrift?: boolean;       // Horizontal drift for snow/leaves
  };
}

🎨 Animation Types

Confetti 🎊

Classic celebration effect with colorful paper pieces.

const buttonRef = useRef<HTMLButtonElement>(null);
const { reward } = useReward(buttonRef, 'confetti', {
  particleCount: 30,
  spread: 60,
  colors: ['#ff0000', '#00ff00', '#0000ff'],
  // NEW: Enable paper flutter effect
  effects: { flutter: true }
});

Sparkles ✨

Twinkling star effect perfect for magical moments.

const { reward } = useReward('buttonId', 'sparkles', {
  particleCount: 20,
  elementSize: 15
});

Hearts 💕

Floating hearts for likes, favorites, or love actions.

const { reward } = useReward('buttonId', 'hearts', {
  particleCount: 15,
  colors: ['#ff1744', '#e91e63', '#ff4569']
});

Stars ⭐

Shooting stars effect for achievements and ratings.

const { reward } = useReward('buttonId', 'stars', {
  particleCount: 25,
  physics: { gravity: 0.3 }
});

Fireworks 🎆

Explosive celebration for major milestones.

const { reward } = useReward('buttonId', 'fireworks', {
  particleCount: 40,
  spread: 180
});

Bubbles 🫧

Gentle floating bubbles for calm, playful effects.

const { reward } = useReward('buttonId', 'bubbles', {
  particleCount: 20,
  physics: { gravity: -0.1 }
});

Snow ❄️

Peaceful falling snowflakes for winter themes.

const { reward } = useReward('buttonId', 'snow', {
  particleCount: 50,
  physics: { gravity: 0.05, wind: 0.1 }
});

Emoji 🎉

Customizable emoji explosions for any mood.

import { useReward, emojiPresets } from 'partycles';

// Use built-in presets
const { reward } = useReward('buttonId', 'emoji', {
  colors: emojiPresets.celebration  // 🎉🎊🥳🎈🎁🍾🥂🎆
});

// Or custom emojis
const { reward } = useReward('buttonId', 'emoji', {
  particleCount: 30,
  colors: ['🎉', '🎊', '🎈', '🎁', '✨']
});

Available emoji presets:

  • emojiPresets.celebration - Party emojis
  • emojiPresets.love - Heart emojis
  • emojiPresets.happy - Happy face emojis
  • emojiPresets.nature - Nature emojis
  • emojiPresets.food - Food emojis

Coins 💰

Falling coins for rewards and achievements.

const { reward } = useReward('buttonId', 'coins', {
  particleCount: 25,
  physics: { gravity: 0.5 }
});

Petals 🌸

Delicate flower petals floating in the wind.

const { reward } = useReward('buttonId', 'petals', {
  particleCount: 40,
  physics: { gravity: 0.08, wind: 0.15 }
});

Aurora 🌌

Northern lights effect with flowing ribbons.

const { reward } = useReward('buttonId', 'aurora', {
  particleCount: 15,
  elementSize: 100
});

Fireflies ✨

Glowing fireflies with organic movement.

const { reward } = useReward('buttonId', 'fireflies', {
  particleCount: 20,
  lifetime: 300
});

Paint 🎨

Paint splatter effect for creative actions.

const { reward } = useReward('buttonId', 'paint', {
  particleCount: 25,
  startVelocity: 35
});

Balloons 🎈

Floating balloons with realistic physics.

const { reward } = useReward('buttonId', 'balloons', {
  particleCount: 15,
  lifetime: 400
});

Galaxy 🌟

Spiral star formation with twinkling effects.

const { reward } = useReward('buttonId', 'galaxy', {
  particleCount: 60,
  spread: 200
});

Glitch 📺

Digital glitch effect for tech themes.

const { reward } = useReward('buttonId', 'glitch', {
  particleCount: 30,
  elementSize: 40
});

Magic Dust ✨

Magical sparkling dust particles.

const { reward } = useReward('buttonId', 'magicdust', {
  particleCount: 35,
  colors: ['#9c27b0', '#673ab7', '#3f51b5']
});

Crystals 💎

Shimmering crystal fragments.

const { reward } = useReward('buttonId', 'crystals', {
  particleCount: 20,
  elementSize: 25
});

Leaves 🍃

Falling autumn leaves with natural movement.

const { reward } = useReward('buttonId', 'leaves', {
  particleCount: 30,
  physics: { gravity: 0.05, wind: 0.2 }
});

Mortar 💥

Explosive mortar-style bursts with galaxy-like explosions.

const { reward } = useReward('buttonId', 'mortar', {
  particleCount: 2,
  spread: 45,
  physics: { gravity: 0.35 }
});

Bokeh 🔮

Soft, dreamy light orbs with depth-of-field effect. Inspired by photography bokeh.

const { reward } = useReward('buttonId', 'bokeh', {
  particleCount: 25,
  elementSize: 40,
  lifetime: 400,
  physics: { gravity: -0.02 }
});

💡 Examples

Animation Controls

Control your animations with pause, resume, and replay:

import { useRef } from 'react';
import { useReward } from 'partycles';

function ControlledAnimation() {
  const buttonRef = useRef<HTMLButtonElement>(null);
  const { reward, isAnimating, pause, resume, replay, isPaused } = useReward(
    buttonRef,
    'confetti',
    { particleCount: 50 }
  );

  return (
    <div>
      <button ref={buttonRef} onClick={reward}>
        Start Animation
      </button>
      
      <button onClick={pause} disabled={!isAnimating || isPaused}>
        Pause
      </button>
      
      <button onClick={resume} disabled={!isAnimating || !isPaused}>
        Resume
      </button>
      
      <button onClick={replay}>
        Replay
      </button>
      
      <p>Status: {isAnimating ? (isPaused ? 'Paused' : 'Playing') : 'Idle'}</p>
    </div>
  );
}

Promise-based Completion

Chain actions after animations complete:

const { reward } = useReward(buttonRef, 'confetti');

const handleSuccess = async () => {
  await reward(); // Wait for animation to complete
  console.log('Animation finished!');
  // Navigate, show message, etc.
};

### Form Submission Success
```tsx
function ContactForm() {
  const { reward } = useReward('submit-btn', 'confetti');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const success = await submitForm();
    if (success) {
      reward();
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* form fields */}
      <button id="submit-btn" type="submit">
        Send Message
      </button>
    </form>
  );
}

Like Button with Hearts

function LikeButton() {
  const [liked, setLiked] = useState(false);
  const { reward } = useReward('like-btn', 'hearts', {
    particleCount: 10,
    elementSize: 20
  });

  const handleLike = () => {
    setLiked(!liked);
    if (!liked) {
      reward();
    }
  };

  return (
    <button id="like-btn" onClick={handleLike}>
      {liked ? '❤️' : '🤍'} Like
    </button>
  );
}

Achievement Unlocked

function Achievement({ unlocked, name }) {
  const { reward } = useReward('achievement', 'fireworks', {
    particleCount: 50,
    spread: 120
  });

  useEffect(() => {
    if (unlocked) {
      reward();
    }
  }, [unlocked, reward]);

  return (
    <div id="achievement" className="achievement-badge">
      🏆 {name}
    </div>
  );
}

✨ Enhanced Effects (v1.1.5+)

Make animations more realistic with optional effects:

// Confetti with paper flutter
const { reward } = useReward('confetti-btn', 'confetti', {
  effects: { flutter: true }
});

// Stars with twinkling
const { reward } = useReward('star-btn', 'stars', {
  effects: { twinkle: true }
});

// Hearts with pulse/heartbeat
const { reward } = useReward('heart-btn', 'hearts', {
  effects: { pulse: true }
});

// Coins with 3D spin
const { reward } = useReward('coin-btn', 'coins', {
  effects: { spin3D: true }
});

// Snow with wind drift
const { reward } = useReward('snow-btn', 'snow', {
  effects: { windDrift: true }
});

// Bubbles with wobble
const { reward } = useReward('bubble-btn', 'bubbles', {
  effects: { wobble: true }
});

// Combine multiple effects
const { reward } = useReward('magic-btn', 'sparkles', {
  effects: { 
    twinkle: true,
    // Other effects work too!
  },
  physics: {
    gravity: 0.2,
    wind: 0.1
  }
});

🎯 Best Practices

  1. Unique IDs: Ensure each animated element has a unique ID
  2. Performance: Avoid triggering multiple animations simultaneously
  3. Accessibility: Provide alternative feedback for users who prefer reduced motion
  4. Mobile: Partycles automatically optimizes for mobile devices
// Respect user preferences
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;

const { reward } = useReward('buttonId', 'confetti', {
  particleCount: prefersReducedMotion ? 0 : 30
});

📱 Mobile Optimization

Partycles automatically detects mobile devices and optimizes performance:

  • Reduced particle counts (60% of desktop)
  • Smaller particle sizes (80% of desktop)
  • Shorter lifetimes (80% of desktop)
  • Frame skipping for smoother performance
  • Tab visibility detection to pause when inactive

You can also manually check for mobile devices:

import { isMobileDevice, optimizeConfigForMobile } from 'partycles';

if (isMobileDevice()) {
  // Custom mobile logic
}

// Or manually optimize a config
const mobileConfig = optimizeConfigForMobile({
  particleCount: 100,
  elementSize: 30
});

🔧 Advanced Usage

Custom Physics

const { reward } = useReward('buttonId', 'confetti', {
  physics: {
    gravity: 0.2,     // Lower gravity = floatier particles
    wind: 0.1,        // Positive = blow right, negative = blow left
    friction: 0.95    // Higher = more air resistance
  }
});

Dynamic Configuration

function DynamicReward() {
  const [intensity, setIntensity] = useState(1);
  
  const { reward } = useReward('buttonId', 'confetti', {
    particleCount: 30 * intensity,
    spread: 50 + (20 * intensity),
    startVelocity: 45 * intensity
  });

  return (
    <>
      <input
        type="range"
        min="0.5"
        max="2"
        step="0.1"
        value={intensity}
        onChange={(e) => setIntensity(parseFloat(e.target.value))}
      />
      <button id="buttonId" onClick={reward}>
        Celebrate!
      </button>
    </>
  );
}

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📈 Changelog

v1.2.6 (Latest)

  • 🎀 Added new ribbons animation with wavy streamers and gradient effects
  • 🔷 Added new geometric animation with modern shapes (triangles, squares, diamonds, hexagons, stars)
  • ✨ Both animations feature satisfying physics inspired by the popular confetti effect
  • 🗑️ Removed fire animation as it didn't work well as a burst effect

v1.2.3

  • 🔮 Updated bokeh animation defaults for better visual impact
  • 📊 Increased default particle count from 25 to 50
  • ⏱️ Reduced lifetime from 400 to 200 for snappier animations
  • 🎯 Adjusted physics for more natural movement

v1.2.2

  • 🔮 Added new bokeh animation with soft, dreamy light orbs
  • 🙏 Added credits section to acknowledge contributors
  • 📱 Optimized bokeh effect for smooth performance

v1.2.1

  • 🎆 Added new mortar animation with galaxy-style explosions
  • 🐛 Fixed particle rendering issues with dynamic particle creation
  • 🎨 Updated emoji assignments for better consistency (fireflies: 🌟, coins: 💰)
  • ⚡ Optimized mortar explosion performance for mobile devices
  • 🔧 Improved animation manager to handle particles created during updates

v1.1.5

  • ✨ Added optional enhanced effects (flutter, twinkle, pulse, spin3D, wobble, windDrift)
  • 📱 Improved mobile performance with automatic optimizations

v1.1.3

  • 🚀 Added automatic mobile performance optimizations
  • 🐛 Fixed Safari compatibility issues
  • 📦 Streamlined bundle size by removing 7 animations
  • 🔧 Fixed CI/CD pipeline and npm publishing

v1.0.0

  • 🎉 Initial release with 26 animations

🙏 Credits

📄 License

MIT © Jonathan Leane


Made with ❤️ by developers, for developers