react-native-glitter 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -164,6 +164,9 @@ function ControlledGlitter() {
164
164
  | `mode` | `'normal' \| 'expand' \| 'shrink'` | `'normal'` | Animation mode for the shimmer line |
165
165
  | `position` | `'top' \| 'center' \| 'bottom'` | `'center'` | Position where the line shrinks/expands (for shrink/expand modes) |
166
166
  | `direction` | `'left-to-right' \| 'right-to-left'` | `'left-to-right'` | Direction of the shimmer animation |
167
+ | `iterations` | `number` | `-1` | Number of animation cycles (-1 for infinite) |
168
+ | `onAnimationStart` | `() => void` | - | Callback when animation starts |
169
+ | `onAnimationComplete` | `() => void` | - | Callback when all iterations complete |
167
170
 
168
171
  ## Examples
169
172
 
@@ -237,6 +240,27 @@ function ControlledGlitter() {
237
240
  </Glitter>
238
241
  ```
239
242
 
243
+ ### Limited Iterations with Callback
244
+
245
+ ```tsx
246
+ // Run 3 times then call onAnimationComplete
247
+ <Glitter
248
+ iterations={3}
249
+ onAnimationStart={() => console.log('Started!')}
250
+ onAnimationComplete={() => console.log('Done!')}
251
+ >
252
+ <View style={styles.box} />
253
+ </Glitter>
254
+
255
+ // Run once (useful for loading states)
256
+ <Glitter
257
+ iterations={1}
258
+ onAnimationComplete={() => setLoading(false)}
259
+ >
260
+ <View style={styles.skeleton} />
261
+ </Glitter>
262
+ ```
263
+
240
264
  ## TypeScript
241
265
 
242
266
  This library is written in TypeScript and includes type definitions:
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState, useCallback, } from 'react';
2
+ import { useEffect, useRef, useState, useCallback, useMemo, memo, } from 'react';
3
3
  import { View, Animated, StyleSheet, Easing, } from 'react-native';
4
4
  function generateGlitterOpacities(count, peak = 1) {
5
5
  const opacities = [];
@@ -48,27 +48,68 @@ function generateVerticalSegments(fadeRatioParam) {
48
48
  }
49
49
  return segments;
50
50
  }
51
- export function Glitter({ 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', }) {
51
+ const HEIGHT_MULTIPLIER = 1.5;
52
+ const NORMAL_FADE_RATIO = (HEIGHT_MULTIPLIER - 1) / HEIGHT_MULTIPLIER / 2;
53
+ const ANIMATED_SEGMENTS = generateVerticalSegments(0.25);
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, }) {
52
56
  const animatedValue = useRef(new Animated.Value(0)).current;
53
57
  const [containerWidth, setContainerWidth] = useState(0);
54
58
  const [containerHeight, setContainerHeight] = useState(0);
55
59
  const animationRef = useRef(null);
56
- const defaultEasing = Easing.bezier(0.4, 0, 0.2, 1);
60
+ const currentIterationRef = useRef(null);
61
+ const iterationCount = useRef(0);
62
+ const isAnimatingRef = useRef(false);
63
+ const defaultEasing = useMemo(() => Easing.bezier(0.4, 0, 0.2, 1), []);
64
+ const stopAnimation = useCallback(() => {
65
+ isAnimatingRef.current = false;
66
+ animationRef.current?.stop();
67
+ animationRef.current = null;
68
+ currentIterationRef.current?.stop();
69
+ currentIterationRef.current = null;
70
+ }, []);
57
71
  const startAnimation = useCallback(() => {
58
72
  if (!active || containerWidth === 0)
59
73
  return;
74
+ stopAnimation();
60
75
  animatedValue.setValue(0);
61
- const timing = Animated.timing(animatedValue, {
62
- toValue: 1,
63
- duration,
64
- useNativeDriver: true,
65
- easing: easing ?? defaultEasing,
66
- });
67
- animationRef.current = Animated.loop(Animated.sequence([timing, Animated.delay(delay)]));
68
- animationRef.current.start();
69
- return () => {
70
- animationRef.current?.stop();
76
+ iterationCount.current = 0;
77
+ isAnimatingRef.current = true;
78
+ onAnimationStart?.();
79
+ const createSingleIteration = () => Animated.sequence([
80
+ Animated.timing(animatedValue, {
81
+ toValue: 1,
82
+ duration,
83
+ useNativeDriver: true,
84
+ easing: easing ?? defaultEasing,
85
+ }),
86
+ Animated.delay(delay),
87
+ ]);
88
+ const runIteration = () => {
89
+ if (!isAnimatingRef.current)
90
+ return;
91
+ animatedValue.setValue(0);
92
+ currentIterationRef.current = createSingleIteration();
93
+ currentIterationRef.current.start(({ finished }) => {
94
+ if (finished && isAnimatingRef.current) {
95
+ iterationCount.current += 1;
96
+ if (iterations === -1 || iterationCount.current < iterations) {
97
+ runIteration();
98
+ }
99
+ else {
100
+ isAnimatingRef.current = false;
101
+ onAnimationComplete?.();
102
+ }
103
+ }
104
+ });
71
105
  };
106
+ if (iterations === -1) {
107
+ animationRef.current = Animated.loop(createSingleIteration());
108
+ animationRef.current.start();
109
+ }
110
+ else {
111
+ runIteration();
112
+ }
72
113
  }, [
73
114
  active,
74
115
  containerWidth,
@@ -77,27 +118,30 @@ export function Glitter({ children, duration = 1500, delay = 400, color = 'rgba(
77
118
  animatedValue,
78
119
  easing,
79
120
  defaultEasing,
121
+ iterations,
122
+ onAnimationStart,
123
+ onAnimationComplete,
124
+ stopAnimation,
80
125
  ]);
81
126
  useEffect(() => {
82
- const cleanup = startAnimation();
83
- return cleanup;
84
- }, [startAnimation]);
127
+ startAnimation();
128
+ return stopAnimation;
129
+ }, [startAnimation, stopAnimation]);
85
130
  const onLayout = useCallback((event) => {
86
131
  const { width, height } = event.nativeEvent.layout;
87
132
  setContainerWidth(width);
88
133
  setContainerHeight(height);
89
134
  }, []);
90
- const extraWidth = Math.tan((angle * Math.PI) / 180) * 200;
135
+ const extraWidth = useMemo(() => Math.tan((angle * Math.PI) / 180) * 200, [angle]);
91
136
  const isLeftToRight = direction === 'left-to-right';
92
- const translateX = animatedValue.interpolate({
137
+ const translateX = useMemo(() => animatedValue.interpolate({
93
138
  inputRange: [0, 1],
94
139
  outputRange: isLeftToRight
95
140
  ? [-shimmerWidth - extraWidth, containerWidth + shimmerWidth]
96
141
  : [containerWidth + shimmerWidth, -shimmerWidth - extraWidth],
97
- });
98
- const heightMultiplier = 1.5;
99
- const lineHeight = containerHeight * heightMultiplier;
100
- const getScaleY = () => {
142
+ }), [animatedValue, isLeftToRight, shimmerWidth, extraWidth, containerWidth]);
143
+ const lineHeight = containerHeight * HEIGHT_MULTIPLIER;
144
+ const scaleY = useMemo(() => {
101
145
  if (mode === 'normal') {
102
146
  return 1;
103
147
  }
@@ -111,67 +155,57 @@ export function Glitter({ children, duration = 1500, delay = 400, color = 'rgba(
111
155
  inputRange: [0, 1],
112
156
  outputRange: [1, 0.01],
113
157
  });
114
- };
158
+ }, [mode, animatedValue]);
115
159
  const halfHeight = lineHeight / 2;
116
- const startOffset = 0;
117
- const getTransformOriginOffset = () => {
160
+ const transformOriginOffset = useMemo(() => {
118
161
  if (mode === 'normal' || position === 'center') {
119
162
  return 0;
120
163
  }
121
- if (position === 'top') {
122
- return -halfHeight;
123
- }
124
- else {
125
- return halfHeight;
126
- }
127
- };
128
- const layerCount = Math.max(11, Math.round(shimmerWidth / 3));
129
- const horizontalOpacities = generateGlitterOpacities(layerCount, 1);
130
- const layerWidth = shimmerWidth / layerCount;
131
- const normalFadeRatio = (heightMultiplier - 1) / heightMultiplier / 2;
132
- const normalSegments = generateVerticalSegments(normalFadeRatio);
133
- const animatedSegments = generateVerticalSegments(0.25);
134
- const shimmerLayers = horizontalOpacities.map((opacity, index) => ({
164
+ return position === 'top' ? -halfHeight : halfHeight;
165
+ }, [mode, position, halfHeight]);
166
+ const layerCount = useMemo(() => Math.max(11, Math.round(shimmerWidth / 3)), [shimmerWidth]);
167
+ const layerWidth = useMemo(() => shimmerWidth / layerCount, [shimmerWidth, layerCount]);
168
+ const horizontalOpacities = useMemo(() => generateGlitterOpacities(layerCount, 1), [layerCount]);
169
+ const shimmerLayers = useMemo(() => horizontalOpacities.map((opacity, index) => ({
135
170
  opacity,
136
171
  position: index * layerWidth - shimmerWidth / 2 + layerWidth / 2,
137
- }));
138
- const scaleY = getScaleY();
139
- const transformOriginOffset = getTransformOriginOffset();
172
+ })), [horizontalOpacities, layerWidth, shimmerWidth]);
140
173
  const isAnimated = mode !== 'normal';
141
- return (_jsxs(View, { style: [styles.container, style], onLayout: onLayout, children: [children, active && containerWidth > 0 && containerHeight > 0 && (_jsx(Animated.View, { style: [
142
- styles.shimmerContainer,
174
+ const segments = isAnimated ? ANIMATED_SEGMENTS : NORMAL_SEGMENTS;
175
+ const shimmerContainerStyle = useMemo(() => [styles.shimmerContainer, { transform: [{ translateX }] }], [translateX]);
176
+ const rotationWrapperStyle = useMemo(() => [
177
+ styles.rotationWrapper,
178
+ {
179
+ width: shimmerWidth,
180
+ height: lineHeight,
181
+ transform: [{ skewX: `${-angle}deg` }],
182
+ },
183
+ ], [shimmerWidth, lineHeight, angle]);
184
+ const getShimmerLineStyle = useCallback((layerPosition) => [
185
+ styles.shimmerLine,
186
+ {
187
+ width: layerWidth + 0.5,
188
+ height: lineHeight,
189
+ left: layerPosition,
190
+ transform: isAnimated
191
+ ? [
192
+ { translateY: transformOriginOffset },
143
193
  {
144
- transform: [{ translateX }],
194
+ scaleY: scaleY,
145
195
  },
146
- ], pointerEvents: "none", children: _jsx(View, { style: [
147
- styles.rotationWrapper,
148
- {
149
- width: shimmerWidth,
150
- height: lineHeight,
151
- transform: [{ skewX: `${-angle}deg` }],
152
- },
153
- ], children: shimmerLayers.map((layer, layerIndex) => (_jsx(Animated.View, { style: [
154
- styles.shimmerLine,
155
- {
156
- width: layerWidth + 0.5,
157
- height: lineHeight,
158
- left: layer.position,
159
- transform: isAnimated
160
- ? [
161
- { translateY: startOffset + transformOriginOffset },
162
- {
163
- scaleY: scaleY,
164
- },
165
- { translateY: -transformOriginOffset },
166
- ]
167
- : [{ translateY: startOffset }],
168
- },
169
- ], children: (isAnimated ? animatedSegments : normalSegments).map((segment, vIndex) => (_jsx(View, { style: {
170
- width: '100%',
171
- height: lineHeight * segment.heightRatio,
172
- backgroundColor: color,
173
- opacity: layer.opacity * segment.opacity,
174
- } }, vIndex))) }, layerIndex))) }) }))] }));
196
+ { translateY: -transformOriginOffset },
197
+ ]
198
+ : [],
199
+ },
200
+ ], [layerWidth, lineHeight, isAnimated, transformOriginOffset, scaleY]);
201
+ return (_jsxs(View, { style: [styles.container, style], onLayout: onLayout, children: [children, active && containerWidth > 0 && 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: segments.map((segment, vIndex) => (_jsx(View, { style: [
202
+ styles.segment,
203
+ {
204
+ height: lineHeight * segment.heightRatio,
205
+ backgroundColor: color,
206
+ opacity: layer.opacity * segment.opacity,
207
+ },
208
+ ] }, vIndex))) }, layerIndex))) }) }))] }));
175
209
  }
176
210
  const styles = StyleSheet.create({
177
211
  container: {
@@ -193,5 +227,9 @@ const styles = StyleSheet.create({
193
227
  position: 'absolute',
194
228
  flexDirection: 'column',
195
229
  },
230
+ segment: {
231
+ width: '100%',
232
+ },
196
233
  });
234
+ export const Glitter = memo(GlitterComponent);
197
235
  export default Glitter;
@@ -16,6 +16,10 @@ export interface GlitterProps {
16
16
  mode?: GlitterMode;
17
17
  position?: GlitterPosition;
18
18
  direction?: GlitterDirection;
19
+ iterations?: number;
20
+ onAnimationStart?: () => void;
21
+ onAnimationComplete?: () => void;
19
22
  }
20
- export declare function Glitter({ children, duration, delay, color, angle, shimmerWidth, active, style, easing, mode, position, direction, }: GlitterProps): ReactElement;
23
+ declare function GlitterComponent({ children, duration, delay, color, angle, shimmerWidth, active, style, easing, mode, position, direction, iterations, onAnimationStart, onAnimationComplete, }: GlitterProps): ReactElement;
24
+ export declare const Glitter: import("react").MemoExoticComponent<typeof GlitterComponent>;
21
25
  export default Glitter;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-glitter",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
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
@@ -3,6 +3,8 @@ import {
3
3
  useRef,
4
4
  useState,
5
5
  useCallback,
6
+ useMemo,
7
+ memo,
6
8
  type ReactNode,
7
9
  type ReactElement,
8
10
  } from 'react';
@@ -35,6 +37,9 @@ export interface GlitterProps {
35
37
  mode?: GlitterMode;
36
38
  position?: GlitterPosition;
37
39
  direction?: GlitterDirection;
40
+ iterations?: number;
41
+ onAnimationStart?: () => void;
42
+ onAnimationComplete?: () => void;
38
43
  }
39
44
 
40
45
  function generateGlitterOpacities(count: number, peak: number = 1): number[] {
@@ -98,7 +103,12 @@ function generateVerticalSegments(fadeRatioParam?: number): VerticalSegment[] {
98
103
  return segments;
99
104
  }
100
105
 
101
- export function Glitter({
106
+ const HEIGHT_MULTIPLIER = 1.5;
107
+ const NORMAL_FADE_RATIO = (HEIGHT_MULTIPLIER - 1) / HEIGHT_MULTIPLIER / 2;
108
+ const ANIMATED_SEGMENTS = generateVerticalSegments(0.25);
109
+ const NORMAL_SEGMENTS = generateVerticalSegments(NORMAL_FADE_RATIO);
110
+
111
+ function GlitterComponent({
102
112
  children,
103
113
  duration = 1500,
104
114
  delay = 400,
@@ -111,35 +121,77 @@ export function Glitter({
111
121
  mode = 'normal',
112
122
  position = 'center',
113
123
  direction = 'left-to-right',
124
+ iterations = -1,
125
+ onAnimationStart,
126
+ onAnimationComplete,
114
127
  }: GlitterProps): ReactElement {
115
128
  const animatedValue = useRef(new Animated.Value(0)).current;
116
129
  const [containerWidth, setContainerWidth] = useState(0);
117
130
  const [containerHeight, setContainerHeight] = useState(0);
118
131
  const animationRef = useRef<ReturnType<typeof Animated.loop> | null>(null);
119
-
120
- const defaultEasing = Easing.bezier(0.4, 0, 0.2, 1);
132
+ const currentIterationRef = useRef<ReturnType<
133
+ typeof Animated.sequence
134
+ > | null>(null);
135
+ const iterationCount = useRef(0);
136
+ const isAnimatingRef = useRef(false);
137
+
138
+ const defaultEasing = useMemo(() => Easing.bezier(0.4, 0, 0.2, 1), []);
139
+
140
+ const stopAnimation = useCallback(() => {
141
+ isAnimatingRef.current = false;
142
+ animationRef.current?.stop();
143
+ animationRef.current = null;
144
+ currentIterationRef.current?.stop();
145
+ currentIterationRef.current = null;
146
+ }, []);
121
147
 
122
148
  const startAnimation = useCallback(() => {
123
149
  if (!active || containerWidth === 0) return;
124
150
 
151
+ stopAnimation();
125
152
  animatedValue.setValue(0);
126
-
127
- const timing = Animated.timing(animatedValue, {
128
- toValue: 1,
129
- duration,
130
- useNativeDriver: true,
131
- easing: easing ?? defaultEasing,
132
- });
133
-
134
- animationRef.current = Animated.loop(
135
- Animated.sequence([timing, Animated.delay(delay)])
136
- );
137
-
138
- animationRef.current.start();
139
-
140
- return () => {
141
- animationRef.current?.stop();
153
+ iterationCount.current = 0;
154
+ isAnimatingRef.current = true;
155
+
156
+ onAnimationStart?.();
157
+
158
+ const createSingleIteration = () =>
159
+ Animated.sequence([
160
+ Animated.timing(animatedValue, {
161
+ toValue: 1,
162
+ duration,
163
+ useNativeDriver: true,
164
+ easing: easing ?? defaultEasing,
165
+ }),
166
+ Animated.delay(delay),
167
+ ]);
168
+
169
+ const runIteration = () => {
170
+ if (!isAnimatingRef.current) return;
171
+
172
+ animatedValue.setValue(0);
173
+ currentIterationRef.current = createSingleIteration();
174
+ currentIterationRef.current.start(
175
+ ({ finished }: { finished: boolean }) => {
176
+ if (finished && isAnimatingRef.current) {
177
+ iterationCount.current += 1;
178
+ if (iterations === -1 || iterationCount.current < iterations) {
179
+ runIteration();
180
+ } else {
181
+ isAnimatingRef.current = false;
182
+ onAnimationComplete?.();
183
+ }
184
+ }
185
+ }
186
+ );
142
187
  };
188
+
189
+ if (iterations === -1) {
190
+ animationRef.current = Animated.loop(createSingleIteration());
191
+ animationRef.current.start();
192
+ } else {
193
+ runIteration();
194
+ }
143
195
  }, [
144
196
  active,
145
197
  containerWidth,
@@ -148,12 +200,16 @@ export function Glitter({
148
200
  animatedValue,
149
201
  easing,
150
202
  defaultEasing,
203
+ iterations,
204
+ onAnimationStart,
205
+ onAnimationComplete,
206
+ stopAnimation,
151
207
  ]);
152
208
 
153
209
  useEffect(() => {
154
- const cleanup = startAnimation();
155
- return cleanup;
156
- }, [startAnimation]);
210
+ startAnimation();
211
+ return stopAnimation;
212
+ }, [startAnimation, stopAnimation]);
157
213
 
158
214
  const onLayout = useCallback((event: LayoutChangeEvent) => {
159
215
  const { width, height } = event.nativeEvent.layout;
@@ -161,20 +217,29 @@ export function Glitter({
161
217
  setContainerHeight(height);
162
218
  }, []);
163
219
 
164
- const extraWidth = Math.tan((angle * Math.PI) / 180) * 200;
220
+ const extraWidth = useMemo(
221
+ () => Math.tan((angle * Math.PI) / 180) * 200,
222
+ [angle]
223
+ );
165
224
 
166
225
  const isLeftToRight = direction === 'left-to-right';
167
- const translateX = animatedValue.interpolate({
168
- inputRange: [0, 1],
169
- outputRange: isLeftToRight
170
- ? [-shimmerWidth - extraWidth, containerWidth + shimmerWidth]
171
- : [containerWidth + shimmerWidth, -shimmerWidth - extraWidth],
172
- });
173
226
 
174
- const heightMultiplier = 1.5;
175
- const lineHeight = containerHeight * heightMultiplier;
227
+ const translateX = useMemo(
228
+ () =>
229
+ animatedValue.interpolate({
230
+ inputRange: [0, 1],
231
+ outputRange: isLeftToRight
232
+ ? [-shimmerWidth - extraWidth, containerWidth + shimmerWidth]
233
+ : [containerWidth + shimmerWidth, -shimmerWidth - extraWidth],
234
+ }),
235
+ [animatedValue, isLeftToRight, shimmerWidth, extraWidth, containerWidth]
236
+ );
176
237
 
177
- const getScaleY = (): Animated.AnimatedInterpolation<number> | number => {
238
+ const lineHeight = containerHeight * HEIGHT_MULTIPLIER;
239
+
240
+ const scaleY = useMemo(():
241
+ | Animated.AnimatedInterpolation<number>
242
+ | number => {
178
243
  if (mode === 'normal') {
179
244
  return 1;
180
245
  }
@@ -190,98 +255,106 @@ export function Glitter({
190
255
  inputRange: [0, 1],
191
256
  outputRange: [1, 0.01],
192
257
  });
193
- };
258
+ }, [mode, animatedValue]);
194
259
 
195
260
  const halfHeight = lineHeight / 2;
196
- const startOffset = 0;
197
261
 
198
- const getTransformOriginOffset = (): number => {
262
+ const transformOriginOffset = useMemo((): number => {
199
263
  if (mode === 'normal' || position === 'center') {
200
264
  return 0;
201
265
  }
266
+ return position === 'top' ? -halfHeight : halfHeight;
267
+ }, [mode, position, halfHeight]);
202
268
 
203
- if (position === 'top') {
204
- return -halfHeight;
205
- } else {
206
- return halfHeight;
207
- }
208
- };
269
+ const layerCount = useMemo(
270
+ () => Math.max(11, Math.round(shimmerWidth / 3)),
271
+ [shimmerWidth]
272
+ );
209
273
 
210
- const layerCount = Math.max(11, Math.round(shimmerWidth / 3));
211
- const horizontalOpacities = generateGlitterOpacities(layerCount, 1);
212
- const layerWidth = shimmerWidth / layerCount;
274
+ const layerWidth = useMemo(
275
+ () => shimmerWidth / layerCount,
276
+ [shimmerWidth, layerCount]
277
+ );
213
278
 
214
- const normalFadeRatio = (heightMultiplier - 1) / heightMultiplier / 2;
215
- const normalSegments = generateVerticalSegments(normalFadeRatio);
216
- const animatedSegments = generateVerticalSegments(0.25);
279
+ const horizontalOpacities = useMemo(
280
+ () => generateGlitterOpacities(layerCount, 1),
281
+ [layerCount]
282
+ );
217
283
 
218
- const shimmerLayers = horizontalOpacities.map((opacity, index) => ({
219
- opacity,
220
- position: index * layerWidth - shimmerWidth / 2 + layerWidth / 2,
221
- }));
284
+ const shimmerLayers = useMemo(
285
+ () =>
286
+ horizontalOpacities.map((opacity, index) => ({
287
+ opacity,
288
+ position: index * layerWidth - shimmerWidth / 2 + layerWidth / 2,
289
+ })),
290
+ [horizontalOpacities, layerWidth, shimmerWidth]
291
+ );
222
292
 
223
- const scaleY = getScaleY();
224
- const transformOriginOffset = getTransformOriginOffset();
225
293
  const isAnimated = mode !== 'normal';
294
+ const segments = isAnimated ? ANIMATED_SEGMENTS : NORMAL_SEGMENTS;
295
+
296
+ const shimmerContainerStyle = useMemo(
297
+ () => [styles.shimmerContainer, { transform: [{ translateX }] }],
298
+ [translateX]
299
+ );
300
+
301
+ const rotationWrapperStyle = useMemo(
302
+ () => [
303
+ styles.rotationWrapper,
304
+ {
305
+ width: shimmerWidth,
306
+ height: lineHeight,
307
+ transform: [{ skewX: `${-angle}deg` }],
308
+ },
309
+ ],
310
+ [shimmerWidth, lineHeight, angle]
311
+ );
312
+
313
+ const getShimmerLineStyle = useCallback(
314
+ (layerPosition: number) => [
315
+ styles.shimmerLine,
316
+ {
317
+ width: layerWidth + 0.5,
318
+ height: lineHeight,
319
+ left: layerPosition,
320
+ transform: isAnimated
321
+ ? [
322
+ { translateY: transformOriginOffset },
323
+ {
324
+ scaleY: scaleY as Animated.AnimatedInterpolation<number>,
325
+ },
326
+ { translateY: -transformOriginOffset },
327
+ ]
328
+ : [],
329
+ },
330
+ ],
331
+ [layerWidth, lineHeight, isAnimated, transformOriginOffset, scaleY]
332
+ );
226
333
 
227
334
  return (
228
335
  <View style={[styles.container, style]} onLayout={onLayout}>
229
336
  {children}
230
337
  {active && containerWidth > 0 && containerHeight > 0 && (
231
- <Animated.View
232
- style={[
233
- styles.shimmerContainer,
234
- {
235
- transform: [{ translateX }],
236
- },
237
- ]}
238
- pointerEvents="none"
239
- >
240
- <View
241
- style={[
242
- styles.rotationWrapper,
243
- {
244
- width: shimmerWidth,
245
- height: lineHeight,
246
- transform: [{ skewX: `${-angle}deg` }],
247
- },
248
- ]}
249
- >
338
+ <Animated.View style={shimmerContainerStyle} pointerEvents="none">
339
+ <View style={rotationWrapperStyle}>
250
340
  {shimmerLayers.map((layer, layerIndex) => (
251
341
  <Animated.View
252
342
  key={layerIndex}
253
- style={[
254
- styles.shimmerLine,
255
- {
256
- width: layerWidth + 0.5,
257
- height: lineHeight,
258
- left: layer.position,
259
- transform: isAnimated
260
- ? [
261
- { translateY: startOffset + transformOriginOffset },
262
- {
263
- scaleY:
264
- scaleY as Animated.AnimatedInterpolation<number>,
265
- },
266
- { translateY: -transformOriginOffset },
267
- ]
268
- : [{ translateY: startOffset }],
269
- },
270
- ]}
343
+ style={getShimmerLineStyle(layer.position)}
271
344
  >
272
- {(isAnimated ? animatedSegments : normalSegments).map(
273
- (segment, vIndex) => (
274
- <View
275
- key={vIndex}
276
- style={{
277
- width: '100%',
345
+ {segments.map((segment, vIndex) => (
346
+ <View
347
+ key={vIndex}
348
+ style={[
349
+ styles.segment,
350
+ {
278
351
  height: lineHeight * segment.heightRatio,
279
352
  backgroundColor: color,
280
353
  opacity: layer.opacity * segment.opacity,
281
- }}
282
- />
283
- )
284
- )}
354
+ },
355
+ ]}
356
+ />
357
+ ))}
285
358
  </Animated.View>
286
359
  ))}
287
360
  </View>
@@ -311,6 +384,11 @@ const styles = StyleSheet.create({
311
384
  position: 'absolute',
312
385
  flexDirection: 'column',
313
386
  },
387
+ segment: {
388
+ width: '100%',
389
+ },
314
390
  });
315
391
 
392
+ export const Glitter = memo(GlitterComponent);
393
+
316
394
  export default Glitter;