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