UNPKG

react-native-confetti-reanimated

Version:

A high-performance confetti component for React Native using Reanimated 4, compatible with Expo

160 lines (144 loc) 4.69 kB
import type { ConfettiConfig, ConfettiParticle } from './types'; // Canvas-confetti uses highly contrasting, distinct colors for realistic effect // Each color is maximally different from others for clear visibility export const DEFAULT_COLORS = [ // Primary vibrant colors (maximally distinct) '#26ccff', // Bright Cyan '#a25afd', // Purple '#ff5e7e', // Pink '#88ff5a', // Lime Green '#fcff42', // Yellow '#ffa62d', // Orange '#ff36ff', // Magenta // Secondary distinct colors '#1e90ff', // Dodger Blue '#9400d3', // Dark Violet '#ff1493', // Deep Pink '#32cd32', // Lime '#ffd700', // Gold '#ff6347', // Tomato '#00ffff', // Cyan '#ff00ff', // Fuchsia // Additional contrast colors '#00ff00', // Pure Green '#ff0000', // Pure Red '#0000ff', // Pure Blue '#ffff00', // Pure Yellow ]; export const DEFAULT_CONFIG: Required<ConfettiConfig> = { particleCount: 50, angle: 90, spread: 45, startVelocity: 45, decay: 0.9, gravity: 1, drift: 0, duration: 3000, colors: DEFAULT_COLORS, scalar: 1, origin: { x: 0.5, y: 0.5 }, shapes: ['square'], // Rectangular strips are most realistic tilt: true, tiltAngleIncrement: 10, tickDuration: 200, disableForReducedMotion: false, usePerformanceMode: false, }; /** * Convert degrees to radians */ export const degreesToRadians = (degrees: number): number => { return (degrees * Math.PI) / 180; }; /** * Generate a random number between min and max */ export const randomRange = (min: number, max: number): number => { return Math.random() * (max - min) + min; }; /** * Pick a random item from an array */ export const randomFromArray = <T>(arr: T[]): T => { const item = arr[Math.floor(Math.random() * arr.length)]; if (item === undefined) { throw new Error('Array is empty'); } return item; }; /** * Create initial confetti particles */ export const createConfettiParticles = ( config: Required<ConfettiConfig>, screenWidth: number, screenHeight: number, ): ConfettiParticle[] => { const particles: ConfettiParticle[] = []; const angleInRadians = degreesToRadians(config.angle); const spreadInRadians = degreesToRadians(config.spread); const timestamp = Date.now(); for (let i = 0; i < config.particleCount; i++) { // Add spread variation to the angle const spreadVariation = randomRange(-spreadInRadians / 2, spreadInRadians / 2); const particleAngle = angleInRadians + spreadVariation; // Randomize velocity within range const velocityMagnitude = config.startVelocity * (0.5 + Math.random() * 0.5); // Create confetti particles - broader and shorter like canvas-confetti const baseWidth = (6 + Math.random() * 4) * config.scalar; // 6-10px wide (BROADER) const aspectRatio = 0.5 + Math.random() * 0.3; // 0.5-0.8 ratio (SHORTER than wide) const particle: ConfettiParticle = { id: `confetti-${timestamp}-${i}-${Math.random()}`, color: randomFromArray(config.colors), shape: randomFromArray(config.shapes), x: (config.origin.x ?? 0.5) * screenWidth, y: (config.origin.y ?? 0.5) * screenHeight, width: baseWidth, height: baseWidth * aspectRatio, // Height is SMALLER than width velocity: { // Canvas-confetti uses pixels per frame (60fps) // Use velocity as-is for proper scaling to device x: Math.cos(particleAngle) * velocityMagnitude, y: -Math.sin(particleAngle) * velocityMagnitude, // Negative = upward initially }, rotation: Math.random() * 360, rotationVelocity: randomRange(-50, 50), // Very wide range for dramatic spinning tiltAngle: config.tilt ? Math.random() * config.tiltAngleIncrement : 0, opacity: 1, }; particles.push(particle); } return particles; }; /** * Update a confetti particle's position */ export const updateParticle = ( particle: ConfettiParticle, config: Required<ConfettiConfig>, deltaTime: number, ): ConfettiParticle => { const dt = deltaTime / 16; // Normalize to 60fps // Apply gravity const newVelocityY = particle.velocity.y - config.gravity * dt; // Apply drift const newVelocityX = particle.velocity.x + config.drift * dt; // Update position const newX = particle.x + newVelocityX * dt; const newY = particle.y - newVelocityY * dt; // Update rotation const newRotation = particle.rotation + particle.rotationVelocity * dt; // Apply decay to opacity const newOpacity = particle.opacity * Math.pow(config.decay, dt); return { ...particle, x: newX, y: newY, velocity: { x: newVelocityX, y: newVelocityY, }, rotation: newRotation, opacity: newOpacity, }; };