react-native-glitter 1.0.7 → 1.0.8
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 +6 -1
- package/lib/module/index.js +42 -9
- package/lib/typescript/src/index.d.ts +27 -0
- package/package.json +1 -1
- package/src/index.tsx +88 -9
package/README.md
CHANGED
|
@@ -17,10 +17,11 @@ Works with both **React Native CLI** and **Expo** projects - no native dependenc
|
|
|
17
17
|
|
|
18
18
|
- 🚀 **Zero native dependencies** - Pure JavaScript/TypeScript implementation
|
|
19
19
|
- 📱 **Cross-platform** - Works on iOS, Android, and Web
|
|
20
|
-
- 🎨 **Customizable** - Control color, speed, angle, and more
|
|
20
|
+
- 🎨 **Customizable** - Control color, speed, angle, opacity, and more
|
|
21
21
|
- ⚡ **Performant** - Uses native driver for smooth 60fps animations
|
|
22
22
|
- 🔧 **TypeScript** - Full TypeScript support with type definitions
|
|
23
23
|
- ✨ **Animation Modes** - Normal, expand, and shrink effects
|
|
24
|
+
- ♿ **Accessible** - Respects system "Reduce Motion" setting
|
|
24
25
|
|
|
25
26
|
## Installation
|
|
26
27
|
|
|
@@ -155,9 +156,11 @@ function ControlledGlitter() {
|
|
|
155
156
|
| `children` | `ReactNode` | **required** | The content to apply the shimmer effect to |
|
|
156
157
|
| `duration` | `number` | `1500` | Duration of one shimmer animation cycle in milliseconds |
|
|
157
158
|
| `delay` | `number` | `400` | Delay between animation cycles in milliseconds |
|
|
159
|
+
| `initialDelay` | `number` | `0` | Initial delay before the first animation starts (useful for staggering) |
|
|
158
160
|
| `color` | `string` | `'rgba(255, 255, 255, 0.8)'` | Color of the shimmer effect |
|
|
159
161
|
| `angle` | `number` | `20` | Angle of the shimmer in degrees |
|
|
160
162
|
| `shimmerWidth` | `number` | `60` | Width of the shimmer band in pixels |
|
|
163
|
+
| `opacity` | `number` | `1` | Overall opacity of the shimmer effect (0-1) |
|
|
161
164
|
| `active` | `boolean` | `true` | Whether the animation is active |
|
|
162
165
|
| `style` | `ViewStyle` | - | Additional styles for the container |
|
|
163
166
|
| `easing` | `(value: number) => number` | - | Custom easing function for the animation |
|
|
@@ -167,10 +170,12 @@ function ControlledGlitter() {
|
|
|
167
170
|
| `iterations` | `number` | `-1` | Number of animation cycles (-1 for infinite) |
|
|
168
171
|
| `onAnimationStart` | `() => void` | - | Callback when animation starts |
|
|
169
172
|
| `onAnimationComplete` | `() => void` | - | Callback when all iterations complete |
|
|
173
|
+
| `onIterationComplete` | `(iteration: number) => void` | - | Callback when each iteration completes |
|
|
170
174
|
| `testID` | `string` | - | Test ID for e2e testing |
|
|
171
175
|
| `accessibilityLabel` | `string` | - | Accessibility label for screen readers |
|
|
172
176
|
| `accessible` | `boolean` | `true` | Whether the component is accessible |
|
|
173
177
|
| `respectReduceMotion` | `boolean` | `true` | Whether to respect the system's "Reduce Motion" setting |
|
|
178
|
+
| `pauseOnBackground` | `boolean` | `true` | Whether to pause animation when app goes to background |
|
|
174
179
|
|
|
175
180
|
## Examples
|
|
176
181
|
|
package/lib/module/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState, useCallback, useMemo, memo, useImperativeHandle, forwardRef, } from 'react';
|
|
3
|
-
import { View, Animated, StyleSheet, Easing, AccessibilityInfo, } from 'react-native';
|
|
3
|
+
import { View, Animated, StyleSheet, Easing, AccessibilityInfo, AppState, } from 'react-native';
|
|
4
4
|
function generateGlitterOpacities(count, peak = 1) {
|
|
5
5
|
const opacities = [];
|
|
6
6
|
const center = (count - 1) / 2;
|
|
@@ -52,11 +52,26 @@ const HEIGHT_MULTIPLIER = 1.5;
|
|
|
52
52
|
const NORMAL_FADE_RATIO = (HEIGHT_MULTIPLIER - 1) / HEIGHT_MULTIPLIER / 2;
|
|
53
53
|
const ANIMATED_SEGMENTS = generateVerticalSegments(0.25);
|
|
54
54
|
const NORMAL_SEGMENTS = generateVerticalSegments(NORMAL_FADE_RATIO);
|
|
55
|
-
function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgba(255, 255, 255, 0.8)', angle = 20, shimmerWidth = 60, active = true, style, easing, mode = 'normal', position = 'center', direction = 'left-to-right', iterations = -1, onAnimationStart, onAnimationComplete, testID, accessibilityLabel, accessible = true, respectReduceMotion = true, }, ref) {
|
|
55
|
+
function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgba(255, 255, 255, 0.8)', angle = 20, shimmerWidth = 60, opacity: shimmerOpacity = 1, active = true, style, easing, mode = 'normal', position = 'center', direction = 'left-to-right', iterations = -1, initialDelay = 0, onAnimationStart, onAnimationComplete, onIterationComplete, testID, accessibilityLabel, accessible = true, respectReduceMotion = true, pauseOnBackground = true, }, ref) {
|
|
56
56
|
const animatedValue = useRef(new Animated.Value(0)).current;
|
|
57
57
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
58
58
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
59
59
|
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false);
|
|
60
|
+
const [isAppActive, setIsAppActive] = useState(true);
|
|
61
|
+
// Detect app state changes (background/foreground)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!pauseOnBackground) {
|
|
64
|
+
setIsAppActive(true);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const handleAppStateChange = (nextAppState) => {
|
|
68
|
+
setIsAppActive(nextAppState === 'active');
|
|
69
|
+
};
|
|
70
|
+
const subscription = AppState.addEventListener('change', handleAppStateChange);
|
|
71
|
+
return () => {
|
|
72
|
+
subscription.remove();
|
|
73
|
+
};
|
|
74
|
+
}, [pauseOnBackground]);
|
|
60
75
|
// Detect system reduce motion preference
|
|
61
76
|
useEffect(() => {
|
|
62
77
|
if (!respectReduceMotion) {
|
|
@@ -85,11 +100,16 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
85
100
|
}, [respectReduceMotion]);
|
|
86
101
|
const animationRef = useRef(null);
|
|
87
102
|
const currentIterationRef = useRef(null);
|
|
103
|
+
const initialDelayRef = useRef(null);
|
|
88
104
|
const iterationCount = useRef(0);
|
|
89
105
|
const isAnimatingRef = useRef(false);
|
|
90
106
|
const defaultEasing = useMemo(() => Easing.bezier(0.4, 0, 0.2, 1), []);
|
|
91
107
|
const stopAnimation = useCallback(() => {
|
|
92
108
|
isAnimatingRef.current = false;
|
|
109
|
+
if (initialDelayRef.current) {
|
|
110
|
+
clearTimeout(initialDelayRef.current);
|
|
111
|
+
initialDelayRef.current = null;
|
|
112
|
+
}
|
|
93
113
|
animationRef.current?.stop();
|
|
94
114
|
animationRef.current = null;
|
|
95
115
|
currentIterationRef.current?.stop();
|
|
@@ -113,13 +133,12 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
113
133
|
isAnimating: () => isAnimatingRef.current,
|
|
114
134
|
}), [stopAnimation, restartAnimation, containerWidth]);
|
|
115
135
|
const startAnimation = useCallback(() => {
|
|
116
|
-
if (!active || containerWidth === 0 || reduceMotionEnabled)
|
|
136
|
+
if (!active || containerWidth === 0 || reduceMotionEnabled || !isAppActive)
|
|
117
137
|
return;
|
|
118
138
|
stopAnimation();
|
|
119
139
|
animatedValue.setValue(0);
|
|
120
140
|
iterationCount.current = 0;
|
|
121
141
|
isAnimatingRef.current = true;
|
|
122
|
-
onAnimationStart?.();
|
|
123
142
|
const createSingleIteration = () => Animated.sequence([
|
|
124
143
|
Animated.timing(animatedValue, {
|
|
125
144
|
toValue: 1,
|
|
@@ -137,6 +156,7 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
137
156
|
currentIterationRef.current.start(({ finished }) => {
|
|
138
157
|
if (finished && isAnimatingRef.current) {
|
|
139
158
|
iterationCount.current += 1;
|
|
159
|
+
onIterationComplete?.(iterationCount.current);
|
|
140
160
|
if (iterations === -1 || iterationCount.current < iterations) {
|
|
141
161
|
runIteration();
|
|
142
162
|
}
|
|
@@ -147,26 +167,35 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
147
167
|
}
|
|
148
168
|
});
|
|
149
169
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
const beginAnimation = () => {
|
|
171
|
+
if (!isAnimatingRef.current)
|
|
172
|
+
return;
|
|
173
|
+
onAnimationStart?.();
|
|
174
|
+
runIteration();
|
|
175
|
+
};
|
|
176
|
+
// Apply initial delay before first animation
|
|
177
|
+
if (initialDelay > 0) {
|
|
178
|
+
initialDelayRef.current = setTimeout(beginAnimation, initialDelay);
|
|
153
179
|
}
|
|
154
180
|
else {
|
|
155
|
-
|
|
181
|
+
beginAnimation();
|
|
156
182
|
}
|
|
157
183
|
}, [
|
|
158
184
|
active,
|
|
159
185
|
containerWidth,
|
|
160
186
|
duration,
|
|
161
187
|
delay,
|
|
188
|
+
initialDelay,
|
|
162
189
|
animatedValue,
|
|
163
190
|
easing,
|
|
164
191
|
defaultEasing,
|
|
165
192
|
iterations,
|
|
166
193
|
onAnimationStart,
|
|
167
194
|
onAnimationComplete,
|
|
195
|
+
onIterationComplete,
|
|
168
196
|
stopAnimation,
|
|
169
197
|
reduceMotionEnabled,
|
|
198
|
+
isAppActive,
|
|
170
199
|
]);
|
|
171
200
|
useEffect(() => {
|
|
172
201
|
startAnimation();
|
|
@@ -225,7 +254,10 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
225
254
|
backgroundColor: color,
|
|
226
255
|
baseOpacity: segment.opacity,
|
|
227
256
|
})), [segments, lineHeight, color]);
|
|
228
|
-
const shimmerContainerStyle = useMemo(() => [
|
|
257
|
+
const shimmerContainerStyle = useMemo(() => [
|
|
258
|
+
styles.shimmerContainer,
|
|
259
|
+
{ transform: [{ translateX }], opacity: shimmerOpacity },
|
|
260
|
+
], [translateX, shimmerOpacity]);
|
|
229
261
|
const rotationWrapperStyle = useMemo(() => [
|
|
230
262
|
styles.rotationWrapper,
|
|
231
263
|
{
|
|
@@ -253,6 +285,7 @@ function GlitterComponent({ children, duration = 1500, delay = 400, color = 'rgb
|
|
|
253
285
|
], [layerWidth, lineHeight, isAnimated, transformOriginOffset, scaleY]);
|
|
254
286
|
return (_jsxs(View, { style: [styles.container, style], onLayout: onLayout, testID: testID, accessibilityLabel: accessibilityLabel, accessible: accessible, children: [children, active &&
|
|
255
287
|
!reduceMotionEnabled &&
|
|
288
|
+
isAppActive &&
|
|
256
289
|
containerWidth > 0 &&
|
|
257
290
|
containerHeight > 0 && (_jsx(Animated.View, { style: shimmerContainerStyle, pointerEvents: "none", children: _jsx(View, { style: rotationWrapperStyle, children: shimmerLayers.map((layer, layerIndex) => (_jsx(Animated.View, { style: getShimmerLineStyle(layer.position), children: segmentStyles.map((segmentStyle, vIndex) => (_jsx(View, { style: [
|
|
258
291
|
styles.segment,
|
|
@@ -51,6 +51,13 @@ export interface GlitterProps {
|
|
|
51
51
|
* @default 400
|
|
52
52
|
*/
|
|
53
53
|
delay?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Initial delay before the first animation cycle starts in milliseconds.
|
|
56
|
+
* Useful for staggering multiple shimmer effects.
|
|
57
|
+
* @default 0
|
|
58
|
+
* @example 500 // Wait 500ms before starting the first animation
|
|
59
|
+
*/
|
|
60
|
+
initialDelay?: number;
|
|
54
61
|
/**
|
|
55
62
|
* Color of the shimmer effect.
|
|
56
63
|
* Supports any valid React Native color format (rgba, hex, rgb, named colors).
|
|
@@ -69,6 +76,13 @@ export interface GlitterProps {
|
|
|
69
76
|
* @default 60
|
|
70
77
|
*/
|
|
71
78
|
shimmerWidth?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Overall opacity of the shimmer effect.
|
|
81
|
+
* Value between 0 and 1.
|
|
82
|
+
* @default 1
|
|
83
|
+
* @example 0.5 // 50% opacity shimmer
|
|
84
|
+
*/
|
|
85
|
+
opacity?: number;
|
|
72
86
|
/**
|
|
73
87
|
* Whether the animation is active.
|
|
74
88
|
* Set to false to pause the animation.
|
|
@@ -119,6 +133,13 @@ export interface GlitterProps {
|
|
|
119
133
|
* Only called when iterations is a positive number.
|
|
120
134
|
*/
|
|
121
135
|
onAnimationComplete?: () => void;
|
|
136
|
+
/**
|
|
137
|
+
* Callback fired when each iteration completes.
|
|
138
|
+
* Receives the current iteration number (1-indexed).
|
|
139
|
+
* @param iteration - The iteration number that just completed (starts at 1)
|
|
140
|
+
* @example (iteration) => console.log(`Iteration ${iteration} completed`)
|
|
141
|
+
*/
|
|
142
|
+
onIterationComplete?: (iteration: number) => void;
|
|
122
143
|
/**
|
|
123
144
|
* Test ID for e2e testing frameworks like Detox.
|
|
124
145
|
*/
|
|
@@ -139,6 +160,12 @@ export interface GlitterProps {
|
|
|
139
160
|
* @default true
|
|
140
161
|
*/
|
|
141
162
|
respectReduceMotion?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* Whether to pause the animation when the app goes to background.
|
|
165
|
+
* Helps save battery by stopping unnecessary animations.
|
|
166
|
+
* @default true
|
|
167
|
+
*/
|
|
168
|
+
pauseOnBackground?: boolean;
|
|
142
169
|
}
|
|
143
170
|
/**
|
|
144
171
|
* Ref methods exposed by the Glitter component for programmatic control.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-glitter",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"description": "A beautiful shimmer/glitter effect component for React Native. Add a sparkling diagonal shine animation to any component!",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
package/src/index.tsx
CHANGED
|
@@ -17,9 +17,11 @@ import {
|
|
|
17
17
|
StyleSheet,
|
|
18
18
|
Easing,
|
|
19
19
|
AccessibilityInfo,
|
|
20
|
+
AppState,
|
|
20
21
|
type LayoutChangeEvent,
|
|
21
22
|
type StyleProp,
|
|
22
23
|
type ViewStyle,
|
|
24
|
+
type AppStateStatus,
|
|
23
25
|
} from 'react-native';
|
|
24
26
|
|
|
25
27
|
/**
|
|
@@ -79,6 +81,14 @@ export interface GlitterProps {
|
|
|
79
81
|
*/
|
|
80
82
|
delay?: number;
|
|
81
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Initial delay before the first animation cycle starts in milliseconds.
|
|
86
|
+
* Useful for staggering multiple shimmer effects.
|
|
87
|
+
* @default 0
|
|
88
|
+
* @example 500 // Wait 500ms before starting the first animation
|
|
89
|
+
*/
|
|
90
|
+
initialDelay?: number;
|
|
91
|
+
|
|
82
92
|
/**
|
|
83
93
|
* Color of the shimmer effect.
|
|
84
94
|
* Supports any valid React Native color format (rgba, hex, rgb, named colors).
|
|
@@ -100,6 +110,14 @@ export interface GlitterProps {
|
|
|
100
110
|
*/
|
|
101
111
|
shimmerWidth?: number;
|
|
102
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Overall opacity of the shimmer effect.
|
|
115
|
+
* Value between 0 and 1.
|
|
116
|
+
* @default 1
|
|
117
|
+
* @example 0.5 // 50% opacity shimmer
|
|
118
|
+
*/
|
|
119
|
+
opacity?: number;
|
|
120
|
+
|
|
103
121
|
/**
|
|
104
122
|
* Whether the animation is active.
|
|
105
123
|
* Set to false to pause the animation.
|
|
@@ -159,6 +177,14 @@ export interface GlitterProps {
|
|
|
159
177
|
*/
|
|
160
178
|
onAnimationComplete?: () => void;
|
|
161
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Callback fired when each iteration completes.
|
|
182
|
+
* Receives the current iteration number (1-indexed).
|
|
183
|
+
* @param iteration - The iteration number that just completed (starts at 1)
|
|
184
|
+
* @example (iteration) => console.log(`Iteration ${iteration} completed`)
|
|
185
|
+
*/
|
|
186
|
+
onIterationComplete?: (iteration: number) => void;
|
|
187
|
+
|
|
162
188
|
/**
|
|
163
189
|
* Test ID for e2e testing frameworks like Detox.
|
|
164
190
|
*/
|
|
@@ -182,6 +208,13 @@ export interface GlitterProps {
|
|
|
182
208
|
* @default true
|
|
183
209
|
*/
|
|
184
210
|
respectReduceMotion?: boolean;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Whether to pause the animation when the app goes to background.
|
|
214
|
+
* Helps save battery by stopping unnecessary animations.
|
|
215
|
+
* @default true
|
|
216
|
+
*/
|
|
217
|
+
pauseOnBackground?: boolean;
|
|
185
218
|
}
|
|
186
219
|
|
|
187
220
|
/**
|
|
@@ -302,6 +335,7 @@ function GlitterComponent(
|
|
|
302
335
|
color = 'rgba(255, 255, 255, 0.8)',
|
|
303
336
|
angle = 20,
|
|
304
337
|
shimmerWidth = 60,
|
|
338
|
+
opacity: shimmerOpacity = 1,
|
|
305
339
|
active = true,
|
|
306
340
|
style,
|
|
307
341
|
easing,
|
|
@@ -309,12 +343,15 @@ function GlitterComponent(
|
|
|
309
343
|
position = 'center',
|
|
310
344
|
direction = 'left-to-right',
|
|
311
345
|
iterations = -1,
|
|
346
|
+
initialDelay = 0,
|
|
312
347
|
onAnimationStart,
|
|
313
348
|
onAnimationComplete,
|
|
349
|
+
onIterationComplete,
|
|
314
350
|
testID,
|
|
315
351
|
accessibilityLabel,
|
|
316
352
|
accessible = true,
|
|
317
353
|
respectReduceMotion = true,
|
|
354
|
+
pauseOnBackground = true,
|
|
318
355
|
}: GlitterProps,
|
|
319
356
|
ref: ForwardedRef<GlitterRef>
|
|
320
357
|
): ReactElement {
|
|
@@ -322,6 +359,28 @@ function GlitterComponent(
|
|
|
322
359
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
323
360
|
const [containerHeight, setContainerHeight] = useState(0);
|
|
324
361
|
const [reduceMotionEnabled, setReduceMotionEnabled] = useState(false);
|
|
362
|
+
const [isAppActive, setIsAppActive] = useState(true);
|
|
363
|
+
|
|
364
|
+
// Detect app state changes (background/foreground)
|
|
365
|
+
useEffect(() => {
|
|
366
|
+
if (!pauseOnBackground) {
|
|
367
|
+
setIsAppActive(true);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
372
|
+
setIsAppActive(nextAppState === 'active');
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const subscription = AppState.addEventListener(
|
|
376
|
+
'change',
|
|
377
|
+
handleAppStateChange
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
return () => {
|
|
381
|
+
subscription.remove();
|
|
382
|
+
};
|
|
383
|
+
}, [pauseOnBackground]);
|
|
325
384
|
|
|
326
385
|
// Detect system reduce motion preference
|
|
327
386
|
useEffect(() => {
|
|
@@ -360,6 +419,7 @@ function GlitterComponent(
|
|
|
360
419
|
const currentIterationRef = useRef<ReturnType<
|
|
361
420
|
typeof Animated.sequence
|
|
362
421
|
> | null>(null);
|
|
422
|
+
const initialDelayRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
363
423
|
const iterationCount = useRef(0);
|
|
364
424
|
const isAnimatingRef = useRef(false);
|
|
365
425
|
|
|
@@ -367,6 +427,10 @@ function GlitterComponent(
|
|
|
367
427
|
|
|
368
428
|
const stopAnimation = useCallback(() => {
|
|
369
429
|
isAnimatingRef.current = false;
|
|
430
|
+
if (initialDelayRef.current) {
|
|
431
|
+
clearTimeout(initialDelayRef.current);
|
|
432
|
+
initialDelayRef.current = null;
|
|
433
|
+
}
|
|
370
434
|
animationRef.current?.stop();
|
|
371
435
|
animationRef.current = null;
|
|
372
436
|
currentIterationRef.current?.stop();
|
|
@@ -397,15 +461,14 @@ function GlitterComponent(
|
|
|
397
461
|
);
|
|
398
462
|
|
|
399
463
|
const startAnimation = useCallback(() => {
|
|
400
|
-
if (!active || containerWidth === 0 || reduceMotionEnabled)
|
|
464
|
+
if (!active || containerWidth === 0 || reduceMotionEnabled || !isAppActive)
|
|
465
|
+
return;
|
|
401
466
|
|
|
402
467
|
stopAnimation();
|
|
403
468
|
animatedValue.setValue(0);
|
|
404
469
|
iterationCount.current = 0;
|
|
405
470
|
isAnimatingRef.current = true;
|
|
406
471
|
|
|
407
|
-
onAnimationStart?.();
|
|
408
|
-
|
|
409
472
|
const createSingleIteration = () =>
|
|
410
473
|
Animated.sequence([
|
|
411
474
|
Animated.timing(animatedValue, {
|
|
@@ -426,6 +489,8 @@ function GlitterComponent(
|
|
|
426
489
|
({ finished }: { finished: boolean }) => {
|
|
427
490
|
if (finished && isAnimatingRef.current) {
|
|
428
491
|
iterationCount.current += 1;
|
|
492
|
+
onIterationComplete?.(iterationCount.current);
|
|
493
|
+
|
|
429
494
|
if (iterations === -1 || iterationCount.current < iterations) {
|
|
430
495
|
runIteration();
|
|
431
496
|
} else {
|
|
@@ -437,25 +502,35 @@ function GlitterComponent(
|
|
|
437
502
|
);
|
|
438
503
|
};
|
|
439
504
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
505
|
+
const beginAnimation = () => {
|
|
506
|
+
if (!isAnimatingRef.current) return;
|
|
507
|
+
|
|
508
|
+
onAnimationStart?.();
|
|
444
509
|
runIteration();
|
|
510
|
+
};
|
|
511
|
+
|
|
512
|
+
// Apply initial delay before first animation
|
|
513
|
+
if (initialDelay > 0) {
|
|
514
|
+
initialDelayRef.current = setTimeout(beginAnimation, initialDelay);
|
|
515
|
+
} else {
|
|
516
|
+
beginAnimation();
|
|
445
517
|
}
|
|
446
518
|
}, [
|
|
447
519
|
active,
|
|
448
520
|
containerWidth,
|
|
449
521
|
duration,
|
|
450
522
|
delay,
|
|
523
|
+
initialDelay,
|
|
451
524
|
animatedValue,
|
|
452
525
|
easing,
|
|
453
526
|
defaultEasing,
|
|
454
527
|
iterations,
|
|
455
528
|
onAnimationStart,
|
|
456
529
|
onAnimationComplete,
|
|
530
|
+
onIterationComplete,
|
|
457
531
|
stopAnimation,
|
|
458
532
|
reduceMotionEnabled,
|
|
533
|
+
isAppActive,
|
|
459
534
|
]);
|
|
460
535
|
|
|
461
536
|
useEffect(() => {
|
|
@@ -559,8 +634,11 @@ function GlitterComponent(
|
|
|
559
634
|
);
|
|
560
635
|
|
|
561
636
|
const shimmerContainerStyle = useMemo(
|
|
562
|
-
() => [
|
|
563
|
-
|
|
637
|
+
() => [
|
|
638
|
+
styles.shimmerContainer,
|
|
639
|
+
{ transform: [{ translateX }], opacity: shimmerOpacity },
|
|
640
|
+
],
|
|
641
|
+
[translateX, shimmerOpacity]
|
|
564
642
|
);
|
|
565
643
|
|
|
566
644
|
const rotationWrapperStyle = useMemo(
|
|
@@ -607,6 +685,7 @@ function GlitterComponent(
|
|
|
607
685
|
{children}
|
|
608
686
|
{active &&
|
|
609
687
|
!reduceMotionEnabled &&
|
|
688
|
+
isAppActive &&
|
|
610
689
|
containerWidth > 0 &&
|
|
611
690
|
containerHeight > 0 && (
|
|
612
691
|
<Animated.View style={shimmerContainerStyle} pointerEvents="none">
|