react-native-confetti-reanimated 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,50 +3,46 @@ import type { ConfettiConfig } from './types';
3
3
  * Predefined confetti presets for common use cases
4
4
  */
5
5
  /**
6
- * Basic celebration with confetti from the center
6
+ * Basic Cannon - The default basic blast of confetti
7
7
  */
8
- export declare const celebration: ConfettiConfig;
8
+ export declare const basicCannon: ConfettiConfig;
9
9
  /**
10
- * Fireworks effect
10
+ * Random Direction - Random amount in random directions
11
11
  */
12
- export declare const fireworks: ConfettiConfig;
12
+ export declare const randomDirection: ConfettiConfig;
13
13
  /**
14
- * Confetti from the bottom
14
+ * Realistic Look - Mix multiple effects to avoid "flattened cone"
15
+ * Fires 3 bursts with varying parameters for natural look
15
16
  */
16
- export declare const bottomCannon: ConfettiConfig;
17
- /**
18
- * Confetti from left side
19
- */
20
- export declare const leftCannon: ConfettiConfig;
17
+ export declare const realistic: ConfettiConfig;
21
18
  /**
22
- * Confetti from right side
19
+ * Fireworks - From the sides
23
20
  */
24
- export declare const rightCannon: ConfettiConfig;
21
+ export declare const fireworks: ConfettiConfig;
25
22
  /**
26
- * Realistic looking confetti
23
+ * Stars - Burst of star shapes
27
24
  */
28
- export declare const realistic: ConfettiConfig;
25
+ export declare const stars: ConfettiConfig;
29
26
  /**
30
- * Snow effect
27
+ * Left Cannon - Confetti from left side
31
28
  */
32
- export declare const snow: ConfettiConfig;
29
+ export declare const leftCannon: ConfettiConfig;
33
30
  /**
34
- * Stars effect
31
+ * Right Cannon - Confetti from right side
35
32
  */
36
- export declare const stars: ConfettiConfig;
33
+ export declare const rightCannon: ConfettiConfig;
37
34
  /**
38
- * School pride (custom colors)
35
+ * Bottom Cannon - Confetti from bottom
39
36
  */
40
- export declare const schoolPride: ConfettiConfig;
37
+ export declare const bottomCannon: ConfettiConfig;
41
38
  export declare const presets: {
42
- celebration: ConfettiConfig;
39
+ basicCannon: ConfettiConfig;
40
+ randomDirection: ConfettiConfig;
41
+ realistic: ConfettiConfig;
43
42
  fireworks: ConfettiConfig;
44
- bottomCannon: ConfettiConfig;
43
+ stars: ConfettiConfig;
45
44
  leftCannon: ConfettiConfig;
46
45
  rightCannon: ConfettiConfig;
47
- realistic: ConfettiConfig;
48
- snow: ConfettiConfig;
49
- stars: ConfettiConfig;
50
- schoolPride: ConfettiConfig;
46
+ bottomCannon: ConfettiConfig;
51
47
  };
52
48
  //# sourceMappingURL=presets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,cAIzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,cAMvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,cAM1B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,cAMxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,cAMzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,cAUvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,IAAI,EAAE,cAWlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,EAAE,cASnB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,cAKzB,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;;;;;;CAUnB,CAAC"}
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,cAIzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,cAS7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,SAAS,EAAE,cAOvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,cAOvB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,EAAE,cASnB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,cAMxB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,cAMzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,cAM1B,CAAC;AAEF,eAAO,MAAM,OAAO;;;;;;;;;CASnB,CAAC"}
@@ -62,7 +62,7 @@ export interface ConfettiConfig {
62
62
  * Shapes for confetti
63
63
  * @default ['square', 'circle']
64
64
  */
65
- shapes?: Array<'square' | 'circle' | 'triangle'>;
65
+ shapes?: Array<'square' | 'circle' | 'star'>;
66
66
  /**
67
67
  * Whether the confetti should be affected by tilt
68
68
  * @default true
@@ -92,10 +92,11 @@ export interface ConfettiConfig {
92
92
  export interface ConfettiParticle {
93
93
  id: string;
94
94
  color: string;
95
- shape: 'square' | 'circle' | 'triangle';
95
+ shape: 'square' | 'circle' | 'star';
96
96
  x: number;
97
97
  y: number;
98
- size: number;
98
+ width: number;
99
+ height: number;
99
100
  velocity: {
100
101
  x: number;
101
102
  y: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QACP,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,CAAC,CAAC,EAAE,MAAM,CAAC;KACZ,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC,CAAC;IAEjD;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,UAAU,CAAC;IACxC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QACR,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAElB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;;OAIG;IACH,MAAM,CAAC,EAAE;QACP,CAAC,CAAC,EAAE,MAAM,CAAC;QACX,CAAC,CAAC,EAAE,MAAM,CAAC;KACZ,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC,CAAC;IAE7C;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAElC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE;QACR,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;KACX,CAAC;IACF,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,CAAC,MAAM,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;OAEG;IACH,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhE,eAAO,MAAM,cAAc,UAQ1B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,cAAc,CAkBnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,MAElD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,MAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,KAAK,CAAC,EAAE,KAAG,CAM7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,QAAQ,CAAC,cAAc,CAAC,EAChC,aAAa,MAAM,EACnB,cAAc,MAAM,KACnB,gBAAgB,EA8BlB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,gBAAgB,EAC1B,QAAQ,QAAQ,CAAC,cAAc,CAAC,EAChC,WAAW,MAAM,KAChB,gBA8BF,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAIhE,eAAO,MAAM,cAAc,UAuB1B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,QAAQ,CAAC,cAAc,CAkBnD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,SAAS,MAAM,KAAG,MAElD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,EAAE,KAAK,MAAM,KAAG,MAEtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,CAAC,EAAE,KAAK,CAAC,EAAE,KAAG,CAM7C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,QAAQ,CAAC,cAAc,CAAC,EAChC,aAAa,MAAM,EACnB,cAAc,MAAM,KACnB,gBAAgB,EAuClB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GACzB,UAAU,gBAAgB,EAC1B,QAAQ,QAAQ,CAAC,cAAc,CAAC,EAChC,WAAW,MAAM,KAChB,gBA6BF,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-confetti-reanimated",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "A high-performance confetti component for React Native using Reanimated 4, compatible with Expo",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -42,23 +42,19 @@
42
42
  },
43
43
  "homepage": "https://github.com/andydev271/react-native-confetti-reanimated#readme",
44
44
  "peerDependencies": {
45
- "react": "*",
46
- "react-native": "*",
47
- "react-native-reanimated": ">=4.0.0",
48
- "react-native-worklets": ">=0.5.0"
45
+ "react": ">=18.0.0",
46
+ "react-native": ">=0.74.0",
47
+ "react-native-reanimated": ">=4.0.0"
49
48
  },
50
49
  "devDependencies": {
51
50
  "@react-native-community/eslint-config": "^3.2.0",
52
- "@types/react": "~18.2.0",
53
- "@types/react-native": "^0.72.0",
54
- "eslint": "^8.51.0",
55
- "prettier": "^3.0.3",
56
- "react": "18.2.0",
57
- "react-native": "0.74.0",
58
- "react-native-builder-bob": "^0.30.0",
51
+ "@types/react": "~19.1.0",
52
+ "prettier": "^3.4.2",
53
+ "react": "19.1.0",
54
+ "react-native": "0.81.5",
55
+ "react-native-builder-bob": "^0.40.0",
59
56
  "react-native-reanimated": "^4.0.0",
60
- "react-native-worklets": "^0.5.0",
61
- "typescript": "^5.3.0"
57
+ "typescript": "^5.7.0"
62
58
  },
63
59
  "react-native-builder-bob": {
64
60
  "source": "src",
@@ -10,7 +10,7 @@ export interface ConfettiCanvasProps {
10
10
  * Style for the confetti container
11
11
  */
12
12
  containerStyle?: any;
13
-
13
+
14
14
  /**
15
15
  * Z-index for the confetti container
16
16
  * @default 1000
@@ -24,10 +24,13 @@ export interface ConfettiCanvasProps {
24
24
  fullScreen?: boolean;
25
25
  }
26
26
 
27
+ interface ParticleWithConfig extends ConfettiParticleType {
28
+ config: Required<ConfettiConfig>;
29
+ }
30
+
27
31
  export const ConfettiCanvas = React.forwardRef<ConfettiMethods, ConfettiCanvasProps>(
28
32
  ({ containerStyle, zIndex = 1000, fullScreen = true }, ref) => {
29
- const [particles, setParticles] = useState<ConfettiParticleType[]>([]);
30
- const [activeCount, setActiveCount] = useState(0);
33
+ const [particles, setParticles] = useState<ParticleWithConfig[]>([]);
31
34
  const { width, height } = useWindowDimensions();
32
35
 
33
36
  const fire = useCallback(
@@ -43,21 +46,23 @@ export const ConfettiCanvas = React.forwardRef<ConfettiMethods, ConfettiCanvasPr
43
46
  };
44
47
 
45
48
  const newParticles = createConfettiParticles(mergedConfig, width, height);
46
- setParticles(prev => [...prev, ...newParticles]);
47
- setActiveCount(prev => prev + newParticles.length);
49
+ const particlesWithConfig = newParticles.map(p => ({
50
+ ...p,
51
+ config: mergedConfig,
52
+ }));
53
+ setParticles(prev => [...prev, ...particlesWithConfig]);
48
54
 
49
55
  // Resolve after the duration
50
56
  setTimeout(() => {
51
- resolve(null);
52
- }, mergedConfig.duration);
57
+ resolve(null);
58
+ }, mergedConfig.duration);
53
59
  });
54
60
  },
55
- [width, height]
61
+ [width, height],
56
62
  );
57
63
 
58
64
  const reset = useCallback(() => {
59
65
  setParticles([]);
60
- setActiveCount(0);
61
66
  }, []);
62
67
 
63
68
  useImperativeHandle(
@@ -67,17 +72,16 @@ export const ConfettiCanvas = React.forwardRef<ConfettiMethods, ConfettiCanvasPr
67
72
  confetti.reset = reset;
68
73
  return confetti;
69
74
  },
70
- [fire, reset]
75
+ [fire, reset],
71
76
  );
72
77
 
73
78
  const handleParticleComplete = useCallback((particleId: string) => {
74
- setActiveCount(prev => prev - 1);
75
79
  setParticles(prev => {
76
80
  // Clean up completed particles periodically
77
81
  if (prev.length > 100) {
78
82
  return prev.filter(p => p.id !== particleId).slice(-50);
79
83
  }
80
- return prev;
84
+ return prev.filter(p => p.id !== particleId);
81
85
  });
82
86
  }, []);
83
87
 
@@ -94,13 +98,14 @@ export const ConfettiCanvas = React.forwardRef<ConfettiMethods, ConfettiCanvasPr
94
98
  <ConfettiParticle
95
99
  key={particle.id}
96
100
  particle={particle}
97
- duration={DEFAULT_CONFIG.duration}
101
+ config={particle.config}
102
+ duration={particle.config.duration}
98
103
  onComplete={() => handleParticleComplete(particle.id)}
99
104
  />
100
105
  ))}
101
106
  </View>
102
107
  );
103
- }
108
+ },
104
109
  );
105
110
 
106
111
  ConfettiCanvas.displayName = 'ConfettiCanvas';
@@ -3,103 +3,192 @@ import { StyleSheet } from 'react-native';
3
3
  import Animated, {
4
4
  useAnimatedStyle,
5
5
  useSharedValue,
6
- withTiming,
7
- Easing,
8
- runOnJS,
6
+ useFrameCallback,
7
+ cancelAnimation,
9
8
  } from 'react-native-reanimated';
10
- import type { ConfettiParticle as ConfettiParticleType } from './types';
9
+ import type { ConfettiParticle as ConfettiParticleType, ConfettiConfig } from './types';
11
10
 
12
11
  interface Props {
13
12
  particle: ConfettiParticleType;
13
+ config: Required<ConfettiConfig>;
14
14
  duration: number;
15
15
  onComplete?: () => void;
16
16
  }
17
17
 
18
- export const ConfettiParticle: React.FC<Props> = ({ particle, duration, onComplete }) => {
18
+ export const ConfettiParticle: React.FC<Props> = ({ particle, config, duration, onComplete }) => {
19
19
  const translateX = useSharedValue(0);
20
20
  const translateY = useSharedValue(0);
21
21
  const rotation = useSharedValue(particle.rotation);
22
22
  const opacity = useSharedValue(1);
23
23
 
24
+ // Velocity state
25
+ const velX = useSharedValue(particle.velocity.x);
26
+ const velY = useSharedValue(particle.velocity.y);
27
+ const startTime = useSharedValue(Date.now());
28
+ const isComplete = useSharedValue(false);
29
+
30
+ // Canvas-confetti realistic wobble and tilt variables
31
+ const wobble = useSharedValue(Math.random() * 10);
32
+ const wobbleSpeed = useSharedValue(Math.min(0.11, Math.random() * 0.1 + 0.05));
33
+ const tiltAngle = useSharedValue(particle.tiltAngle);
34
+ const tiltSin = useSharedValue(0);
35
+ const tiltCos = useSharedValue(0);
36
+ const random = useSharedValue(Math.random() + 2);
37
+ const tick = useSharedValue(0);
38
+ const totalTicks = useSharedValue((duration / 1000) * 60); // 60fps
39
+
24
40
  useEffect(() => {
25
- // Animate the particle
26
- translateX.value = withTiming(particle.velocity.x * (duration / 16), {
27
- duration,
28
- easing: Easing.linear,
29
- });
30
-
31
- translateY.value = withTiming(particle.velocity.y * (duration / 16), {
32
- duration,
33
- easing: Easing.bezier(0.33, 1, 0.68, 1), // Custom easing for gravity effect
34
- });
35
-
36
- rotation.value = withTiming(
37
- particle.rotation + particle.rotationVelocity * (duration / 16),
38
- {
39
- duration,
40
- easing: Easing.linear,
41
- }
42
- );
41
+ startTime.value = Date.now();
42
+ tick.value = 0;
43
43
 
44
- opacity.value = withTiming(
45
- 0,
46
- {
47
- duration,
48
- easing: Easing.linear,
49
- },
50
- finished => {
51
- if (finished && onComplete) {
52
- runOnJS(onComplete)();
53
- }
44
+ // Cleanup callback
45
+ const timer = setTimeout(() => {
46
+ isComplete.value = true;
47
+ if (onComplete) {
48
+ onComplete();
54
49
  }
55
- );
56
- }, [duration, onComplete, opacity, particle, rotation, translateX, translateY]);
50
+ }, duration);
51
+
52
+ return () => {
53
+ clearTimeout(timer);
54
+ cancelAnimation(translateX);
55
+ cancelAnimation(translateY);
56
+ cancelAnimation(rotation);
57
+ cancelAnimation(opacity);
58
+ };
59
+ }, [duration, onComplete, opacity, isComplete, startTime, translateX, translateY, rotation, tick]);
60
+
61
+ // Real-time physics simulation using frame callback
62
+ useFrameCallback(() => {
63
+ 'worklet';
64
+
65
+ if (isComplete.value) {
66
+ return;
67
+ }
68
+
69
+ const elapsed = Date.now() - startTime.value;
70
+ if (elapsed >= duration) {
71
+ isComplete.value = true;
72
+ return;
73
+ }
74
+
75
+ // Update position based on current velocity
76
+ translateX.value += velX.value;
77
+ translateY.value += velY.value;
78
+
79
+ // Apply gravity (increases downward velocity) - realistic physics!
80
+ velY.value += config.gravity;
81
+
82
+ // Apply drift (horizontal wind)
83
+ velX.value += config.drift;
84
+
85
+ // Apply decay (air resistance)
86
+ velX.value *= config.decay;
87
+ velY.value *= config.decay;
88
+
89
+ // Update rotation - ALL particles spin faster when moving fast, slower when slowing down
90
+ const speed = Math.sqrt(velX.value * velX.value + velY.value * velY.value);
91
+ const speedBoost = 1 + speed / 20;
92
+ rotation.value += particle.rotationVelocity * speedBoost;
93
+
94
+ // Canvas-confetti wobble effect (creates side-to-side flutter)
95
+ wobble.value += wobbleSpeed.value;
96
+
97
+ // Canvas-confetti tilt animation (creates 3D tumbling effect)
98
+ tiltAngle.value += 0.1;
99
+ tiltSin.value = Math.sin(tiltAngle.value);
100
+ tiltCos.value = Math.cos(tiltAngle.value);
101
+ random.value = Math.random() + 2;
102
+
103
+ // Update tick for progressive opacity fade
104
+ tick.value += 1;
105
+
106
+ // Canvas-confetti progressive fade: opacity decreases linearly over lifetime
107
+ const progress = tick.value / totalTicks.value;
108
+ opacity.value = 1 - progress;
109
+ });
57
110
 
58
111
  const animatedStyle = useAnimatedStyle(() => {
112
+ // Canvas-confetti wobble calculation (circular motion)
113
+ const wobbleX = 10 * config.scalar * Math.cos(wobble.value);
114
+ const wobbleY = 10 * config.scalar * Math.sin(wobble.value);
115
+
116
+ // Canvas-confetti 3D-like positioning with tilt
117
+ const x1 = translateX.value + random.value * tiltCos.value;
118
+ const y1 = translateY.value + random.value * tiltSin.value;
119
+ const x2 = translateX.value + wobbleX + random.value * tiltCos.value;
120
+ const y2 = translateY.value + wobbleY + random.value * tiltSin.value;
121
+
122
+ // Dynamic scaling based on wobble (creates 3D depth perception)
123
+ const scaleX = Math.abs(x2 - x1) * 0.1;
124
+ const scaleY = Math.abs(y2 - y1) * 0.1;
125
+
59
126
  return {
60
127
  transform: [
61
- { translateX: translateX.value },
62
- { translateY: translateY.value },
128
+ { translateX: x2 },
129
+ { translateY: y2 },
63
130
  { rotate: `${rotation.value}deg` },
131
+ { scaleX: Math.max(0.3, scaleX) }, // Prevent too small
132
+ { scaleY: Math.max(0.3, scaleY) },
64
133
  ],
65
134
  opacity: opacity.value,
66
135
  };
67
136
  });
68
137
 
69
138
  const renderShape = () => {
70
- const baseStyle = [
71
- styles.particle,
72
- {
73
- width: particle.size,
74
- height: particle.size,
75
- backgroundColor: particle.color,
76
- },
77
- ];
78
-
79
139
  if (particle.shape === 'circle') {
140
+ // Circles for snow - perfectly round
141
+ const size = particle.width;
80
142
  return (
81
- <Animated.View style={[...baseStyle, styles.circle, animatedStyle]} />
143
+ <Animated.View
144
+ style={[
145
+ styles.particle,
146
+ styles.circle,
147
+ {
148
+ width: size,
149
+ height: size,
150
+ backgroundColor: particle.color,
151
+ },
152
+ animatedStyle,
153
+ ]}
154
+ />
82
155
  );
83
- } else if (particle.shape === 'triangle') {
156
+ }
157
+
158
+ if (particle.shape === 'star') {
159
+ // Stars using Unicode character
160
+ const fontSize = particle.width * 1.5;
84
161
  return (
85
- <Animated.View style={[...baseStyle, styles.transparent, animatedStyle]}>
86
- <Animated.View
87
- style={[
88
- styles.triangle,
89
- {
90
- borderLeftWidth: particle.size / 2,
91
- borderRightWidth: particle.size / 2,
92
- borderBottomWidth: particle.size,
93
- borderBottomColor: particle.color,
94
- },
95
- ]}
96
- />
97
- </Animated.View>
162
+ <Animated.Text
163
+ style={[
164
+ styles.particle,
165
+ styles.star,
166
+ {
167
+ fontSize,
168
+ color: particle.color,
169
+ textShadowColor: particle.color,
170
+ },
171
+ animatedStyle,
172
+ ]}>
173
+
174
+ </Animated.Text>
98
175
  );
99
176
  }
100
177
 
101
- // Square (default)
102
- return <Animated.View style={[...baseStyle, animatedStyle]} />;
178
+ // Rectangles - sharp, thin confetti strips
179
+ return (
180
+ <Animated.View
181
+ style={[
182
+ styles.particle,
183
+ {
184
+ width: particle.width,
185
+ height: particle.height,
186
+ backgroundColor: particle.color,
187
+ },
188
+ animatedStyle,
189
+ ]}
190
+ />
191
+ );
103
192
  };
104
193
 
105
194
  return (
@@ -126,16 +215,8 @@ const styles = StyleSheet.create({
126
215
  circle: {
127
216
  borderRadius: 999,
128
217
  },
129
- transparent: {
130
- backgroundColor: 'transparent',
131
- },
132
- triangle: {
133
- width: 0,
134
- height: 0,
135
- backgroundColor: 'transparent',
136
- borderStyle: 'solid',
137
- borderLeftColor: 'transparent',
138
- borderRightColor: 'transparent',
218
+ star: {
219
+ textShadowOffset: { width: 0, height: 0 },
220
+ textShadowRadius: 3,
139
221
  },
140
222
  });
141
-