react-native-confetti-reanimated 0.1.0 → 0.1.2
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.
- package/README.md +38 -38
- package/lib/commonjs/ConfettiCanvas.js +8 -7
- package/lib/commonjs/ConfettiCanvas.js.map +1 -1
- package/lib/commonjs/ConfettiParticle.js +134 -53
- package/lib/commonjs/ConfettiParticle.js.map +1 -1
- package/lib/commonjs/presets.js +65 -79
- package/lib/commonjs/presets.js.map +1 -1
- package/lib/commonjs/utils.js +65 -10
- package/lib/commonjs/utils.js.map +1 -1
- package/lib/module/ConfettiCanvas.js +8 -7
- package/lib/module/ConfettiCanvas.js.map +1 -1
- package/lib/module/ConfettiParticle.js +135 -54
- package/lib/module/ConfettiParticle.js.map +1 -1
- package/lib/module/presets.js +64 -78
- package/lib/module/presets.js.map +1 -1
- package/lib/module/utils.js +65 -10
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/ConfettiCanvas.d.ts.map +1 -1
- package/lib/typescript/ConfettiParticle.d.ts +2 -1
- package/lib/typescript/ConfettiParticle.d.ts.map +1 -1
- package/lib/typescript/presets.d.ts +22 -26
- package/lib/typescript/presets.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +4 -3
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/utils.d.ts.map +1 -1
- package/package.json +10 -14
- package/src/ConfettiCanvas.tsx +19 -14
- package/src/ConfettiParticle.tsx +153 -75
- package/src/presets.ts +56 -70
- package/src/types.ts +4 -3
- package/src/utils.ts +44 -20
- package/lib/module/package.json +0 -1
|
@@ -3,50 +3,46 @@ import type { ConfettiConfig } from './types';
|
|
|
3
3
|
* Predefined confetti presets for common use cases
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* Basic
|
|
6
|
+
* Basic Cannon - The default basic blast of confetti
|
|
7
7
|
*/
|
|
8
|
-
export declare const
|
|
8
|
+
export declare const basicCannon: ConfettiConfig;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
10
|
+
* Random Direction - Random amount in random directions
|
|
11
11
|
*/
|
|
12
|
-
export declare const
|
|
12
|
+
export declare const randomDirection: ConfettiConfig;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
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
|
|
17
|
-
/**
|
|
18
|
-
* Confetti from left side
|
|
19
|
-
*/
|
|
20
|
-
export declare const leftCannon: ConfettiConfig;
|
|
17
|
+
export declare const realistic: ConfettiConfig;
|
|
21
18
|
/**
|
|
22
|
-
*
|
|
19
|
+
* Fireworks - From the sides
|
|
23
20
|
*/
|
|
24
|
-
export declare const
|
|
21
|
+
export declare const fireworks: ConfettiConfig;
|
|
25
22
|
/**
|
|
26
|
-
*
|
|
23
|
+
* Stars - Burst of star shapes
|
|
27
24
|
*/
|
|
28
|
-
export declare const
|
|
25
|
+
export declare const stars: ConfettiConfig;
|
|
29
26
|
/**
|
|
30
|
-
*
|
|
27
|
+
* Left Cannon - Confetti from left side
|
|
31
28
|
*/
|
|
32
|
-
export declare const
|
|
29
|
+
export declare const leftCannon: ConfettiConfig;
|
|
33
30
|
/**
|
|
34
|
-
*
|
|
31
|
+
* Right Cannon - Confetti from right side
|
|
35
32
|
*/
|
|
36
|
-
export declare const
|
|
33
|
+
export declare const rightCannon: ConfettiConfig;
|
|
37
34
|
/**
|
|
38
|
-
*
|
|
35
|
+
* Bottom Cannon - Confetti from bottom
|
|
39
36
|
*/
|
|
40
|
-
export declare const
|
|
37
|
+
export declare const bottomCannon: ConfettiConfig;
|
|
41
38
|
export declare const presets: {
|
|
42
|
-
|
|
39
|
+
basicCannon: ConfettiConfig;
|
|
40
|
+
randomDirection: ConfettiConfig;
|
|
41
|
+
realistic: ConfettiConfig;
|
|
43
42
|
fireworks: ConfettiConfig;
|
|
44
|
-
|
|
43
|
+
stars: ConfettiConfig;
|
|
45
44
|
leftCannon: ConfettiConfig;
|
|
46
45
|
rightCannon: ConfettiConfig;
|
|
47
|
-
|
|
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,
|
|
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' | '
|
|
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' | '
|
|
95
|
+
shape: 'square' | 'circle' | 'star';
|
|
96
96
|
x: number;
|
|
97
97
|
y: number;
|
|
98
|
-
|
|
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,
|
|
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;
|
|
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.
|
|
3
|
+
"version": "0.1.2",
|
|
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": "~
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"react": "
|
|
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
|
-
"
|
|
61
|
-
"typescript": "^5.3.0"
|
|
57
|
+
"typescript": "^5.7.0"
|
|
62
58
|
},
|
|
63
59
|
"react-native-builder-bob": {
|
|
64
60
|
"source": "src",
|
package/src/ConfettiCanvas.tsx
CHANGED
|
@@ -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<
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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';
|
package/src/ConfettiParticle.tsx
CHANGED
|
@@ -3,103 +3,193 @@ import { StyleSheet } from 'react-native';
|
|
|
3
3
|
import Animated, {
|
|
4
4
|
useAnimatedStyle,
|
|
5
5
|
useSharedValue,
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
26
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
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:
|
|
62
|
-
{ translateY:
|
|
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
|
|
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
|
-
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (particle.shape === 'star') {
|
|
159
|
+
// Stars using Unicode character
|
|
160
|
+
const fontSize = particle.width * 1.5;
|
|
84
161
|
return (
|
|
85
|
-
<Animated.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
162
|
+
<Animated.Text
|
|
163
|
+
style={[
|
|
164
|
+
styles.particle,
|
|
165
|
+
{
|
|
166
|
+
fontSize,
|
|
167
|
+
color: particle.color,
|
|
168
|
+
textShadowColor: particle.color,
|
|
169
|
+
textShadowOffset: { width: 0, height: 0 },
|
|
170
|
+
textShadowRadius: 3,
|
|
171
|
+
},
|
|
172
|
+
animatedStyle,
|
|
173
|
+
]}>
|
|
174
|
+
★
|
|
175
|
+
</Animated.Text>
|
|
98
176
|
);
|
|
99
177
|
}
|
|
100
178
|
|
|
101
|
-
//
|
|
102
|
-
return
|
|
179
|
+
// Rectangles - sharp, thin confetti strips
|
|
180
|
+
return (
|
|
181
|
+
<Animated.View
|
|
182
|
+
style={[
|
|
183
|
+
styles.particle,
|
|
184
|
+
{
|
|
185
|
+
width: particle.width,
|
|
186
|
+
height: particle.height,
|
|
187
|
+
backgroundColor: particle.color,
|
|
188
|
+
},
|
|
189
|
+
animatedStyle,
|
|
190
|
+
]}
|
|
191
|
+
/>
|
|
192
|
+
);
|
|
103
193
|
};
|
|
104
194
|
|
|
105
195
|
return (
|
|
@@ -126,16 +216,4 @@ const styles = StyleSheet.create({
|
|
|
126
216
|
circle: {
|
|
127
217
|
borderRadius: 999,
|
|
128
218
|
},
|
|
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',
|
|
139
|
-
},
|
|
140
219
|
});
|
|
141
|
-
|