react-native-confetti-reanimated 0.1.0

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.
Files changed (54) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +277 -0
  3. package/lib/commonjs/ConfettiCanvas.js +89 -0
  4. package/lib/commonjs/ConfettiCanvas.js.map +1 -0
  5. package/lib/commonjs/ConfettiParticle.js +116 -0
  6. package/lib/commonjs/ConfettiParticle.js.map +1 -0
  7. package/lib/commonjs/index.js +27 -0
  8. package/lib/commonjs/index.js.map +1 -0
  9. package/lib/commonjs/package.json +1 -0
  10. package/lib/commonjs/presets.js +146 -0
  11. package/lib/commonjs/presets.js.map +1 -0
  12. package/lib/commonjs/types.js +2 -0
  13. package/lib/commonjs/types.js.map +1 -0
  14. package/lib/commonjs/useConfetti.js +27 -0
  15. package/lib/commonjs/useConfetti.js.map +1 -0
  16. package/lib/commonjs/utils.js +125 -0
  17. package/lib/commonjs/utils.js.map +1 -0
  18. package/lib/module/ConfettiCanvas.js +84 -0
  19. package/lib/module/ConfettiCanvas.js.map +1 -0
  20. package/lib/module/ConfettiParticle.js +110 -0
  21. package/lib/module/ConfettiParticle.js.map +1 -0
  22. package/lib/module/index.js +6 -0
  23. package/lib/module/index.js.map +1 -0
  24. package/lib/module/package.json +1 -0
  25. package/lib/module/presets.js +142 -0
  26. package/lib/module/presets.js.map +1 -0
  27. package/lib/module/types.js +2 -0
  28. package/lib/module/types.js.map +1 -0
  29. package/lib/module/useConfetti.js +22 -0
  30. package/lib/module/useConfetti.js.map +1 -0
  31. package/lib/module/utils.js +116 -0
  32. package/lib/module/utils.js.map +1 -0
  33. package/lib/typescript/ConfettiCanvas.d.ts +20 -0
  34. package/lib/typescript/ConfettiCanvas.d.ts.map +1 -0
  35. package/lib/typescript/ConfettiParticle.d.ts +10 -0
  36. package/lib/typescript/ConfettiParticle.d.ts.map +1 -0
  37. package/lib/typescript/index.d.ts +6 -0
  38. package/lib/typescript/index.d.ts.map +1 -0
  39. package/lib/typescript/presets.d.ts +52 -0
  40. package/lib/typescript/presets.d.ts.map +1 -0
  41. package/lib/typescript/types.d.ts +118 -0
  42. package/lib/typescript/types.d.ts.map +1 -0
  43. package/lib/typescript/useConfetti.d.ts +11 -0
  44. package/lib/typescript/useConfetti.d.ts.map +1 -0
  45. package/lib/typescript/utils.d.ts +24 -0
  46. package/lib/typescript/utils.d.ts.map +1 -0
  47. package/package.json +78 -0
  48. package/src/ConfettiCanvas.tsx +121 -0
  49. package/src/ConfettiParticle.tsx +141 -0
  50. package/src/index.tsx +6 -0
  51. package/src/presets.ts +126 -0
  52. package/src/types.ts +137 -0
  53. package/src/useConfetti.ts +25 -0
  54. package/src/utils.ts +135 -0
@@ -0,0 +1,121 @@
1
+ import React, { useCallback, useImperativeHandle, useState } from 'react';
2
+ import { StyleSheet, View, useWindowDimensions } from 'react-native';
3
+ import type { ConfettiConfig, ConfettiMethods } from './types';
4
+ import { createConfettiParticles, DEFAULT_CONFIG } from './utils';
5
+ import { ConfettiParticle } from './ConfettiParticle';
6
+ import type { ConfettiParticle as ConfettiParticleType } from './types';
7
+
8
+ export interface ConfettiCanvasProps {
9
+ /**
10
+ * Style for the confetti container
11
+ */
12
+ containerStyle?: any;
13
+
14
+ /**
15
+ * Z-index for the confetti container
16
+ * @default 1000
17
+ */
18
+ zIndex?: number;
19
+
20
+ /**
21
+ * Whether confetti should be allowed to go outside of safe area
22
+ * @default true
23
+ */
24
+ fullScreen?: boolean;
25
+ }
26
+
27
+ export const ConfettiCanvas = React.forwardRef<ConfettiMethods, ConfettiCanvasProps>(
28
+ ({ containerStyle, zIndex = 1000, fullScreen = true }, ref) => {
29
+ const [particles, setParticles] = useState<ConfettiParticleType[]>([]);
30
+ const [activeCount, setActiveCount] = useState(0);
31
+ const { width, height } = useWindowDimensions();
32
+
33
+ const fire = useCallback(
34
+ (config: ConfettiConfig = {}): Promise<null> => {
35
+ return new Promise(resolve => {
36
+ const mergedConfig = {
37
+ ...DEFAULT_CONFIG,
38
+ ...config,
39
+ origin: {
40
+ ...DEFAULT_CONFIG.origin,
41
+ ...config.origin,
42
+ },
43
+ };
44
+
45
+ const newParticles = createConfettiParticles(mergedConfig, width, height);
46
+ setParticles(prev => [...prev, ...newParticles]);
47
+ setActiveCount(prev => prev + newParticles.length);
48
+
49
+ // Resolve after the duration
50
+ setTimeout(() => {
51
+ resolve(null);
52
+ }, mergedConfig.duration);
53
+ });
54
+ },
55
+ [width, height]
56
+ );
57
+
58
+ const reset = useCallback(() => {
59
+ setParticles([]);
60
+ setActiveCount(0);
61
+ }, []);
62
+
63
+ useImperativeHandle(
64
+ ref,
65
+ () => {
66
+ const confetti = fire as ConfettiMethods;
67
+ confetti.reset = reset;
68
+ return confetti;
69
+ },
70
+ [fire, reset]
71
+ );
72
+
73
+ const handleParticleComplete = useCallback((particleId: string) => {
74
+ setActiveCount(prev => prev - 1);
75
+ setParticles(prev => {
76
+ // Clean up completed particles periodically
77
+ if (prev.length > 100) {
78
+ return prev.filter(p => p.id !== particleId).slice(-50);
79
+ }
80
+ return prev;
81
+ });
82
+ }, []);
83
+
84
+ return (
85
+ <View
86
+ style={[
87
+ styles.container,
88
+ fullScreen && styles.fullScreen,
89
+ { zIndex },
90
+ containerStyle,
91
+ ]}
92
+ pointerEvents="none">
93
+ {particles.map(particle => (
94
+ <ConfettiParticle
95
+ key={particle.id}
96
+ particle={particle}
97
+ duration={DEFAULT_CONFIG.duration}
98
+ onComplete={() => handleParticleComplete(particle.id)}
99
+ />
100
+ ))}
101
+ </View>
102
+ );
103
+ }
104
+ );
105
+
106
+ ConfettiCanvas.displayName = 'ConfettiCanvas';
107
+
108
+ const styles = StyleSheet.create({
109
+ container: {
110
+ position: 'absolute',
111
+ width: '100%',
112
+ height: '100%',
113
+ },
114
+ fullScreen: {
115
+ top: 0,
116
+ left: 0,
117
+ right: 0,
118
+ bottom: 0,
119
+ },
120
+ });
121
+
@@ -0,0 +1,141 @@
1
+ import React, { useEffect } from 'react';
2
+ import { StyleSheet } from 'react-native';
3
+ import Animated, {
4
+ useAnimatedStyle,
5
+ useSharedValue,
6
+ withTiming,
7
+ Easing,
8
+ runOnJS,
9
+ } from 'react-native-reanimated';
10
+ import type { ConfettiParticle as ConfettiParticleType } from './types';
11
+
12
+ interface Props {
13
+ particle: ConfettiParticleType;
14
+ duration: number;
15
+ onComplete?: () => void;
16
+ }
17
+
18
+ export const ConfettiParticle: React.FC<Props> = ({ particle, duration, onComplete }) => {
19
+ const translateX = useSharedValue(0);
20
+ const translateY = useSharedValue(0);
21
+ const rotation = useSharedValue(particle.rotation);
22
+ const opacity = useSharedValue(1);
23
+
24
+ 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
+ );
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
+ }
54
+ }
55
+ );
56
+ }, [duration, onComplete, opacity, particle, rotation, translateX, translateY]);
57
+
58
+ const animatedStyle = useAnimatedStyle(() => {
59
+ return {
60
+ transform: [
61
+ { translateX: translateX.value },
62
+ { translateY: translateY.value },
63
+ { rotate: `${rotation.value}deg` },
64
+ ],
65
+ opacity: opacity.value,
66
+ };
67
+ });
68
+
69
+ 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
+ if (particle.shape === 'circle') {
80
+ return (
81
+ <Animated.View style={[...baseStyle, styles.circle, animatedStyle]} />
82
+ );
83
+ } else if (particle.shape === 'triangle') {
84
+ 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>
98
+ );
99
+ }
100
+
101
+ // Square (default)
102
+ return <Animated.View style={[...baseStyle, animatedStyle]} />;
103
+ };
104
+
105
+ return (
106
+ <Animated.View
107
+ style={[
108
+ styles.container,
109
+ {
110
+ left: particle.x,
111
+ top: particle.y,
112
+ },
113
+ ]}>
114
+ {renderShape()}
115
+ </Animated.View>
116
+ );
117
+ };
118
+
119
+ const styles = StyleSheet.create({
120
+ container: {
121
+ position: 'absolute',
122
+ },
123
+ particle: {
124
+ position: 'absolute',
125
+ },
126
+ circle: {
127
+ borderRadius: 999,
128
+ },
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
+ });
141
+
package/src/index.tsx ADDED
@@ -0,0 +1,6 @@
1
+ export { ConfettiCanvas } from './ConfettiCanvas';
2
+ export { useConfetti } from './useConfetti';
3
+ export { presets } from './presets';
4
+ export type { ConfettiConfig, ConfettiMethods, ConfettiParticle } from './types';
5
+ export type { ConfettiCanvasProps } from './ConfettiCanvas';
6
+
package/src/presets.ts ADDED
@@ -0,0 +1,126 @@
1
+ import type { ConfettiConfig } from './types';
2
+
3
+ /**
4
+ * Predefined confetti presets for common use cases
5
+ */
6
+
7
+ /**
8
+ * Basic celebration with confetti from the center
9
+ */
10
+ export const celebration: ConfettiConfig = {
11
+ particleCount: 100,
12
+ spread: 70,
13
+ origin: { y: 0.6 },
14
+ };
15
+
16
+ /**
17
+ * Fireworks effect
18
+ */
19
+ export const fireworks: ConfettiConfig = {
20
+ particleCount: 150,
21
+ spread: 360,
22
+ startVelocity: 30,
23
+ decay: 0.94,
24
+ scalar: 1.2,
25
+ };
26
+
27
+ /**
28
+ * Confetti from the bottom
29
+ */
30
+ export const bottomCannon: ConfettiConfig = {
31
+ particleCount: 50,
32
+ angle: 60,
33
+ spread: 55,
34
+ origin: { y: 0.8, x: 0.5 },
35
+ startVelocity: 55,
36
+ };
37
+
38
+ /**
39
+ * Confetti from left side
40
+ */
41
+ export const leftCannon: ConfettiConfig = {
42
+ particleCount: 50,
43
+ angle: 45,
44
+ spread: 55,
45
+ origin: { x: 0, y: 0.6 },
46
+ startVelocity: 55,
47
+ };
48
+
49
+ /**
50
+ * Confetti from right side
51
+ */
52
+ export const rightCannon: ConfettiConfig = {
53
+ particleCount: 50,
54
+ angle: 135,
55
+ spread: 55,
56
+ origin: { x: 1, y: 0.6 },
57
+ startVelocity: 55,
58
+ };
59
+
60
+ /**
61
+ * Realistic looking confetti
62
+ */
63
+ export const realistic: ConfettiConfig = {
64
+ particleCount: 200,
65
+ spread: 160,
66
+ origin: { y: 0.5 },
67
+ startVelocity: 35,
68
+ gravity: 1.5,
69
+ drift: 1,
70
+ tilt: true,
71
+ shapes: ['square', 'circle', 'triangle'],
72
+ scalar: 0.8,
73
+ };
74
+
75
+ /**
76
+ * Snow effect
77
+ */
78
+ export const snow: ConfettiConfig = {
79
+ particleCount: 100,
80
+ spread: 180,
81
+ origin: { y: -0.1 },
82
+ startVelocity: 0,
83
+ gravity: 0.3,
84
+ drift: 1,
85
+ colors: ['#ffffff', '#e8f4ff', '#c9e5ff'],
86
+ shapes: ['circle'],
87
+ scalar: 0.6,
88
+ angle: 270,
89
+ };
90
+
91
+ /**
92
+ * Stars effect
93
+ */
94
+ export const stars: ConfettiConfig = {
95
+ particleCount: 50,
96
+ spread: 360,
97
+ startVelocity: 20,
98
+ decay: 0.95,
99
+ gravity: 0.5,
100
+ colors: ['#FFD700', '#FFA500', '#FFFF00'],
101
+ shapes: ['circle'],
102
+ scalar: 0.5,
103
+ };
104
+
105
+ /**
106
+ * School pride (custom colors)
107
+ */
108
+ export const schoolPride: ConfettiConfig = {
109
+ particleCount: 100,
110
+ spread: 160,
111
+ origin: { y: 0.6 },
112
+ colors: ['#bb0000', '#ffffff'],
113
+ };
114
+
115
+ export const presets = {
116
+ celebration,
117
+ fireworks,
118
+ bottomCannon,
119
+ leftCannon,
120
+ rightCannon,
121
+ realistic,
122
+ snow,
123
+ stars,
124
+ schoolPride,
125
+ };
126
+
package/src/types.ts ADDED
@@ -0,0 +1,137 @@
1
+ export interface ConfettiConfig {
2
+ /**
3
+ * The number of confetti pieces to launch
4
+ * @default 50
5
+ */
6
+ particleCount?: number;
7
+
8
+ /**
9
+ * The angle in degrees at which to launch the confetti
10
+ * @default 90
11
+ */
12
+ angle?: number;
13
+
14
+ /**
15
+ * How far the confetti can spread from the origin
16
+ * @default 45
17
+ */
18
+ spread?: number;
19
+
20
+ /**
21
+ * The starting velocity of the confetti
22
+ * @default 45
23
+ */
24
+ startVelocity?: number;
25
+
26
+ /**
27
+ * How fast the confetti decays
28
+ * @default 0.9
29
+ */
30
+ decay?: number;
31
+
32
+ /**
33
+ * Gravity to apply to the confetti
34
+ * @default 1
35
+ */
36
+ gravity?: number;
37
+
38
+ /**
39
+ * How much to the side the confetti will drift
40
+ * @default 0
41
+ */
42
+ drift?: number;
43
+
44
+ /**
45
+ * Time in milliseconds to run the confetti animation
46
+ * @default 3000
47
+ */
48
+ duration?: number;
49
+
50
+ /**
51
+ * Array of color strings, in any format (hex, rgb, hsl)
52
+ * @default ['#26ccff', '#a25afd', '#ff5e7e', '#88ff5a', '#fcff42', '#ffa62d', '#ff36ff']
53
+ */
54
+ colors?: string[];
55
+
56
+ /**
57
+ * Scale of the confetti particles
58
+ * @default 1
59
+ */
60
+ scalar?: number;
61
+
62
+ /**
63
+ * The x position on the screen where confetti will originate (0-1)
64
+ * 0 is left edge, 0.5 is center, 1 is right edge
65
+ * @default 0.5
66
+ */
67
+ origin?: {
68
+ x?: number;
69
+ y?: number;
70
+ };
71
+
72
+ /**
73
+ * Shapes for confetti
74
+ * @default ['square', 'circle']
75
+ */
76
+ shapes?: Array<'square' | 'circle' | 'triangle'>;
77
+
78
+ /**
79
+ * Whether the confetti should be affected by tilt
80
+ * @default true
81
+ */
82
+ tilt?: boolean;
83
+
84
+ /**
85
+ * Max angle for tilt
86
+ * @default 10
87
+ */
88
+ tiltAngleIncrement?: number;
89
+
90
+ /**
91
+ * The number of times the confetti will move
92
+ * @default 200
93
+ */
94
+ tickDuration?: number;
95
+
96
+ /**
97
+ * Disable physics
98
+ * @default false
99
+ */
100
+ disableForReducedMotion?: boolean;
101
+
102
+ /**
103
+ * Use performance mode (fewer updates)
104
+ * @default false
105
+ */
106
+ usePerformanceMode?: boolean;
107
+ }
108
+
109
+ export interface ConfettiParticle {
110
+ id: string;
111
+ color: string;
112
+ shape: 'square' | 'circle' | 'triangle';
113
+ x: number;
114
+ y: number;
115
+ size: number;
116
+ velocity: {
117
+ x: number;
118
+ y: number;
119
+ };
120
+ rotation: number;
121
+ rotationVelocity: number;
122
+ tiltAngle: number;
123
+ opacity: number;
124
+ }
125
+
126
+ export interface ConfettiMethods {
127
+ /**
128
+ * Fire confetti with the given configuration
129
+ */
130
+ (config?: ConfettiConfig): Promise<null>;
131
+
132
+ /**
133
+ * Reset confetti
134
+ */
135
+ reset: () => void;
136
+ }
137
+
@@ -0,0 +1,25 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import type { ConfettiConfig, ConfettiMethods } from './types';
3
+
4
+ /**
5
+ * Hook to use confetti imperatively
6
+ * Returns a ref to pass to ConfettiCanvas and a fire function
7
+ */
8
+ export const useConfetti = () => {
9
+ const confettiRef = useRef<ConfettiMethods>(null);
10
+
11
+ const fire = useCallback((config?: ConfettiConfig) => {
12
+ return confettiRef.current?.(config);
13
+ }, []);
14
+
15
+ const reset = useCallback(() => {
16
+ confettiRef.current?.reset();
17
+ }, []);
18
+
19
+ return {
20
+ confettiRef,
21
+ fire,
22
+ reset,
23
+ };
24
+ };
25
+
package/src/utils.ts ADDED
@@ -0,0 +1,135 @@
1
+ import type { ConfettiConfig, ConfettiParticle } from './types';
2
+
3
+ export const DEFAULT_COLORS = [
4
+ '#26ccff',
5
+ '#a25afd',
6
+ '#ff5e7e',
7
+ '#88ff5a',
8
+ '#fcff42',
9
+ '#ffa62d',
10
+ '#ff36ff',
11
+ ];
12
+
13
+ export const DEFAULT_CONFIG: Required<ConfettiConfig> = {
14
+ particleCount: 50,
15
+ angle: 90,
16
+ spread: 45,
17
+ startVelocity: 45,
18
+ decay: 0.9,
19
+ gravity: 1,
20
+ drift: 0,
21
+ duration: 3000,
22
+ colors: DEFAULT_COLORS,
23
+ scalar: 1,
24
+ origin: { x: 0.5, y: 0.5 },
25
+ shapes: ['square', 'circle'],
26
+ tilt: true,
27
+ tiltAngleIncrement: 10,
28
+ tickDuration: 200,
29
+ disableForReducedMotion: false,
30
+ usePerformanceMode: false,
31
+ };
32
+
33
+ /**
34
+ * Convert degrees to radians
35
+ */
36
+ export const degreesToRadians = (degrees: number): number => {
37
+ return (degrees * Math.PI) / 180;
38
+ };
39
+
40
+ /**
41
+ * Generate a random number between min and max
42
+ */
43
+ export const randomRange = (min: number, max: number): number => {
44
+ return Math.random() * (max - min) + min;
45
+ };
46
+
47
+ /**
48
+ * Pick a random item from an array
49
+ */
50
+ export const randomFromArray = <T>(arr: T[]): T => {
51
+ const item = arr[Math.floor(Math.random() * arr.length)];
52
+ if (item === undefined) {
53
+ throw new Error('Array is empty');
54
+ }
55
+ return item;
56
+ };
57
+
58
+ /**
59
+ * Create initial confetti particles
60
+ */
61
+ export const createConfettiParticles = (
62
+ config: Required<ConfettiConfig>,
63
+ screenWidth: number,
64
+ screenHeight: number
65
+ ): ConfettiParticle[] => {
66
+ const particles: ConfettiParticle[] = [];
67
+ const angleInRadians = degreesToRadians(config.angle);
68
+ const spreadInRadians = degreesToRadians(config.spread);
69
+
70
+ for (let i = 0; i < config.particleCount; i++) {
71
+ const angle = angleInRadians + randomRange(-spreadInRadians / 2, spreadInRadians / 2);
72
+ const velocity = config.startVelocity * (0.5 + Math.random() * 0.5);
73
+
74
+ const particle: ConfettiParticle = {
75
+ id: `confetti-${i}-${Date.now()}`,
76
+ color: randomFromArray(config.colors),
77
+ shape: randomFromArray(config.shapes),
78
+ x: (config.origin.x ?? 0.5) * screenWidth,
79
+ y: (config.origin.y ?? 0.5) * screenHeight,
80
+ size: (5 + Math.random() * 5) * config.scalar,
81
+ velocity: {
82
+ x: Math.cos(angle) * velocity,
83
+ y: Math.sin(angle) * velocity,
84
+ },
85
+ rotation: Math.random() * 360,
86
+ rotationVelocity: randomRange(-10, 10),
87
+ tiltAngle: config.tilt ? Math.random() * config.tiltAngleIncrement : 0,
88
+ opacity: 1,
89
+ };
90
+
91
+ particles.push(particle);
92
+ }
93
+
94
+ return particles;
95
+ };
96
+
97
+ /**
98
+ * Update a confetti particle's position
99
+ */
100
+ export const updateParticle = (
101
+ particle: ConfettiParticle,
102
+ config: Required<ConfettiConfig>,
103
+ deltaTime: number
104
+ ): ConfettiParticle => {
105
+ const dt = deltaTime / 16; // Normalize to 60fps
106
+
107
+ // Apply gravity
108
+ const newVelocityY = particle.velocity.y - config.gravity * dt;
109
+
110
+ // Apply drift
111
+ const newVelocityX = particle.velocity.x + config.drift * dt;
112
+
113
+ // Update position
114
+ const newX = particle.x + newVelocityX * dt;
115
+ const newY = particle.y - newVelocityY * dt;
116
+
117
+ // Update rotation
118
+ const newRotation = particle.rotation + particle.rotationVelocity * dt;
119
+
120
+ // Apply decay to opacity
121
+ const newOpacity = particle.opacity * Math.pow(config.decay, dt);
122
+
123
+ return {
124
+ ...particle,
125
+ x: newX,
126
+ y: newY,
127
+ velocity: {
128
+ x: newVelocityX,
129
+ y: newVelocityY,
130
+ },
131
+ rotation: newRotation,
132
+ opacity: newOpacity,
133
+ };
134
+ };
135
+