react-native-glitter 1.0.1 → 1.0.4

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,7 +165,11 @@ 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 |
170
+ | `testID` | `string` | - | Test ID for e2e testing |
171
+ | `accessibilityLabel` | `string` | - | Accessibility label for screen readers |
172
+ | `accessible` | `boolean` | `true` | Whether the component is accessible |
169
173
 
170
174
  ## Examples
171
175
 
@@ -245,6 +249,7 @@ function ControlledGlitter() {
245
249
  // Run 3 times then call onAnimationComplete
246
250
  <Glitter
247
251
  iterations={3}
252
+ onAnimationStart={() => console.log('Started!')}
248
253
  onAnimationComplete={() => console.log('Done!')}
249
254
  >
250
255
  <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, testID, accessibilityLabel, accessible = true, }) {
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, testID: testID, accessibilityLabel: accessibilityLabel, accessible: accessible, 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,15 @@ export interface GlitterProps {
17
17
  position?: GlitterPosition;
18
18
  direction?: GlitterDirection;
19
19
  iterations?: number;
20
+ onAnimationStart?: () => void;
20
21
  onAnimationComplete?: () => void;
22
+ /** Test ID for e2e testing */
23
+ testID?: string;
24
+ /** Accessibility label for screen readers */
25
+ accessibilityLabel?: string;
26
+ /** Whether the component is accessible (default: true) */
27
+ accessible?: boolean;
21
28
  }
22
- export declare function Glitter({ children, duration, delay, color, angle, shimmerWidth, active, style, easing, mode, position, direction, iterations, onAnimationComplete, }: GlitterProps): ReactElement;
29
+ declare function GlitterComponent({ children, duration, delay, color, angle, shimmerWidth, active, style, easing, mode, position, direction, iterations, onAnimationStart, onAnimationComplete, testID, accessibilityLabel, accessible, }: GlitterProps): ReactElement;
30
+ export declare const Glitter: import("react").MemoExoticComponent<typeof GlitterComponent>;
23
31
  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.4",
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,7 +38,14 @@ export interface GlitterProps {
36
38
  position?: GlitterPosition;
37
39
  direction?: GlitterDirection;
38
40
  iterations?: number;
41
+ onAnimationStart?: () => void;
39
42
  onAnimationComplete?: () => void;
43
+ /** Test ID for e2e testing */
44
+ testID?: string;
45
+ /** Accessibility label for screen readers */
46
+ accessibilityLabel?: string;
47
+ /** Whether the component is accessible (default: true) */
48
+ accessible?: boolean;
40
49
  }
41
50
 
42
51
  function generateGlitterOpacities(count: number, peak: number = 1): number[] {
@@ -100,7 +109,12 @@ function generateVerticalSegments(fadeRatioParam?: number): VerticalSegment[] {
100
109
  return segments;
101
110
  }
102
111
 
103
- export function Glitter({
112
+ const HEIGHT_MULTIPLIER = 1.5;
113
+ const NORMAL_FADE_RATIO = (HEIGHT_MULTIPLIER - 1) / HEIGHT_MULTIPLIER / 2;
114
+ const ANIMATED_SEGMENTS = generateVerticalSegments(0.25);
115
+ const NORMAL_SEGMENTS = generateVerticalSegments(NORMAL_FADE_RATIO);
116
+
117
+ function GlitterComponent({
104
118
  children,
105
119
  duration = 1500,
106
120
  delay = 400,
@@ -114,57 +128,79 @@ export function Glitter({
114
128
  position = 'center',
115
129
  direction = 'left-to-right',
116
130
  iterations = -1,
131
+ onAnimationStart,
117
132
  onAnimationComplete,
133
+ testID,
134
+ accessibilityLabel,
135
+ accessible = true,
118
136
  }: GlitterProps): ReactElement {
119
137
  const animatedValue = useRef(new Animated.Value(0)).current;
120
138
  const [containerWidth, setContainerWidth] = useState(0);
121
139
  const [containerHeight, setContainerHeight] = useState(0);
122
- const animationRef = useRef<{ start: () => void; stop: () => void } | null>(
123
- null
124
- );
140
+ const animationRef = useRef<ReturnType<typeof Animated.loop> | null>(null);
141
+ const currentIterationRef = useRef<ReturnType<
142
+ typeof Animated.sequence
143
+ > | null>(null);
125
144
  const iterationCount = useRef(0);
145
+ const isAnimatingRef = useRef(false);
126
146
 
127
- const defaultEasing = Easing.bezier(0.4, 0, 0.2, 1);
147
+ const defaultEasing = useMemo(() => Easing.bezier(0.4, 0, 0.2, 1), []);
148
+
149
+ const stopAnimation = useCallback(() => {
150
+ isAnimatingRef.current = false;
151
+ animationRef.current?.stop();
152
+ animationRef.current = null;
153
+ currentIterationRef.current?.stop();
154
+ currentIterationRef.current = null;
155
+ }, []);
128
156
 
129
157
  const startAnimation = useCallback(() => {
130
158
  if (!active || containerWidth === 0) return;
131
159
 
160
+ stopAnimation();
132
161
  animatedValue.setValue(0);
133
162
  iterationCount.current = 0;
163
+ isAnimatingRef.current = true;
134
164
 
135
- const timing = Animated.timing(animatedValue, {
136
- toValue: 1,
137
- duration,
138
- useNativeDriver: true,
139
- easing: easing ?? defaultEasing,
140
- });
165
+ onAnimationStart?.();
141
166
 
142
- const singleIteration = Animated.sequence([timing, Animated.delay(delay)]);
167
+ const createSingleIteration = () =>
168
+ Animated.sequence([
169
+ Animated.timing(animatedValue, {
170
+ toValue: 1,
171
+ duration,
172
+ useNativeDriver: true,
173
+ easing: easing ?? defaultEasing,
174
+ }),
175
+ Animated.delay(delay),
176
+ ]);
143
177
 
144
178
  const runIteration = () => {
179
+ if (!isAnimatingRef.current) return;
180
+
145
181
  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?.();
182
+ currentIterationRef.current = createSingleIteration();
183
+ currentIterationRef.current.start(
184
+ ({ finished }: { finished: boolean }) => {
185
+ if (finished && isAnimatingRef.current) {
186
+ iterationCount.current += 1;
187
+ if (iterations === -1 || iterationCount.current < iterations) {
188
+ runIteration();
189
+ } else {
190
+ isAnimatingRef.current = false;
191
+ onAnimationComplete?.();
192
+ }
153
193
  }
154
194
  }
155
- });
195
+ );
156
196
  };
157
197
 
158
198
  if (iterations === -1) {
159
- animationRef.current = Animated.loop(singleIteration);
199
+ animationRef.current = Animated.loop(createSingleIteration());
160
200
  animationRef.current.start();
161
201
  } else {
162
202
  runIteration();
163
203
  }
164
-
165
- return () => {
166
- animationRef.current?.stop();
167
- };
168
204
  }, [
169
205
  active,
170
206
  containerWidth,
@@ -174,13 +210,15 @@ export function Glitter({
174
210
  easing,
175
211
  defaultEasing,
176
212
  iterations,
213
+ onAnimationStart,
177
214
  onAnimationComplete,
215
+ stopAnimation,
178
216
  ]);
179
217
 
180
218
  useEffect(() => {
181
- const cleanup = startAnimation();
182
- return cleanup;
183
- }, [startAnimation]);
219
+ startAnimation();
220
+ return stopAnimation;
221
+ }, [startAnimation, stopAnimation]);
184
222
 
185
223
  const onLayout = useCallback((event: LayoutChangeEvent) => {
186
224
  const { width, height } = event.nativeEvent.layout;
@@ -188,20 +226,29 @@ export function Glitter({
188
226
  setContainerHeight(height);
189
227
  }, []);
190
228
 
191
- const extraWidth = Math.tan((angle * Math.PI) / 180) * 200;
229
+ const extraWidth = useMemo(
230
+ () => Math.tan((angle * Math.PI) / 180) * 200,
231
+ [angle]
232
+ );
192
233
 
193
234
  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
235
 
201
- const heightMultiplier = 1.5;
202
- const lineHeight = containerHeight * heightMultiplier;
236
+ const translateX = useMemo(
237
+ () =>
238
+ animatedValue.interpolate({
239
+ inputRange: [0, 1],
240
+ outputRange: isLeftToRight
241
+ ? [-shimmerWidth - extraWidth, containerWidth + shimmerWidth]
242
+ : [containerWidth + shimmerWidth, -shimmerWidth - extraWidth],
243
+ }),
244
+ [animatedValue, isLeftToRight, shimmerWidth, extraWidth, containerWidth]
245
+ );
246
+
247
+ const lineHeight = containerHeight * HEIGHT_MULTIPLIER;
203
248
 
204
- const getScaleY = (): Animated.AnimatedInterpolation<number> | number => {
249
+ const scaleY = useMemo(():
250
+ | Animated.AnimatedInterpolation<number>
251
+ | number => {
205
252
  if (mode === 'normal') {
206
253
  return 1;
207
254
  }
@@ -217,100 +264,112 @@ export function Glitter({
217
264
  inputRange: [0, 1],
218
265
  outputRange: [1, 0.01],
219
266
  });
220
- };
267
+ }, [mode, animatedValue]);
221
268
 
222
269
  const halfHeight = lineHeight / 2;
223
- const startOffset = 0;
224
270
 
225
- const getTransformOriginOffset = (): number => {
271
+ const transformOriginOffset = useMemo((): number => {
226
272
  if (mode === 'normal' || position === 'center') {
227
273
  return 0;
228
274
  }
275
+ return position === 'top' ? -halfHeight : halfHeight;
276
+ }, [mode, position, halfHeight]);
229
277
 
230
- if (position === 'top') {
231
- return -halfHeight;
232
- } else {
233
- return halfHeight;
234
- }
235
- };
278
+ const layerCount = useMemo(
279
+ () => Math.max(11, Math.round(shimmerWidth / 3)),
280
+ [shimmerWidth]
281
+ );
236
282
 
237
- const layerCount = Math.max(11, Math.round(shimmerWidth / 3));
238
- const horizontalOpacities = generateGlitterOpacities(layerCount, 1);
239
- const layerWidth = shimmerWidth / layerCount;
283
+ const layerWidth = useMemo(
284
+ () => shimmerWidth / layerCount,
285
+ [shimmerWidth, layerCount]
286
+ );
240
287
 
241
- const normalFadeRatio = (heightMultiplier - 1) / heightMultiplier / 2;
242
- const normalSegments = generateVerticalSegments(normalFadeRatio);
243
- const animatedSegments = generateVerticalSegments(0.25);
288
+ const horizontalOpacities = useMemo(
289
+ () => generateGlitterOpacities(layerCount, 1),
290
+ [layerCount]
291
+ );
244
292
 
245
- const shimmerLayers = horizontalOpacities.map((opacity, index) => ({
246
- opacity,
247
- position: index * layerWidth - shimmerWidth / 2 + layerWidth / 2,
248
- }));
293
+ const shimmerLayers = useMemo(
294
+ () =>
295
+ horizontalOpacities.map((opacity, index) => ({
296
+ opacity,
297
+ position: index * layerWidth - shimmerWidth / 2 + layerWidth / 2,
298
+ })),
299
+ [horizontalOpacities, layerWidth, shimmerWidth]
300
+ );
249
301
 
250
- const scaleY = getScaleY();
251
- const transformOriginOffset = getTransformOriginOffset();
252
302
  const isAnimated = mode !== 'normal';
303
+ const segments = isAnimated ? ANIMATED_SEGMENTS : NORMAL_SEGMENTS;
304
+
305
+ const shimmerContainerStyle = useMemo(
306
+ () => [styles.shimmerContainer, { transform: [{ translateX }] }],
307
+ [translateX]
308
+ );
309
+
310
+ const rotationWrapperStyle = useMemo(
311
+ () => [
312
+ styles.rotationWrapper,
313
+ {
314
+ width: shimmerWidth,
315
+ height: lineHeight,
316
+ transform: [{ skewX: `${-angle}deg` }],
317
+ },
318
+ ],
319
+ [shimmerWidth, lineHeight, angle]
320
+ );
321
+
322
+ const getShimmerLineStyle = useCallback(
323
+ (layerPosition: number) => [
324
+ styles.shimmerLine,
325
+ {
326
+ width: layerWidth + 0.5,
327
+ height: lineHeight,
328
+ left: layerPosition,
329
+ transform: isAnimated
330
+ ? [
331
+ { translateY: transformOriginOffset },
332
+ {
333
+ scaleY: scaleY as Animated.AnimatedInterpolation<number>,
334
+ },
335
+ { translateY: -transformOriginOffset },
336
+ ]
337
+ : [],
338
+ },
339
+ ],
340
+ [layerWidth, lineHeight, isAnimated, transformOriginOffset, scaleY]
341
+ );
253
342
 
254
343
  return (
255
- <View style={[styles.container, style]} onLayout={onLayout}>
344
+ <View
345
+ style={[styles.container, style]}
346
+ onLayout={onLayout}
347
+ testID={testID}
348
+ accessibilityLabel={accessibilityLabel}
349
+ accessible={accessible}
350
+ >
256
351
  {children}
257
352
  {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
- >
353
+ <Animated.View style={shimmerContainerStyle} pointerEvents="none">
354
+ <View style={rotationWrapperStyle}>
277
355
  {shimmerLayers.map((layer, layerIndex) => (
278
356
  <Animated.View
279
357
  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
- ]}
358
+ style={getShimmerLineStyle(layer.position)}
298
359
  >
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
- )}
360
+ {segments.map((segment, vIndex) => (
361
+ <View
362
+ key={vIndex}
363
+ style={[
364
+ styles.segment,
365
+ {
366
+ height: lineHeight * segment.heightRatio,
367
+ backgroundColor: color,
368
+ opacity: layer.opacity * segment.opacity,
369
+ },
370
+ ]}
371
+ />
372
+ ))}
314
373
  </Animated.View>
315
374
  ))}
316
375
  </View>
@@ -345,4 +404,6 @@ const styles = StyleSheet.create({
345
404
  },
346
405
  });
347
406
 
407
+ export const Glitter = memo(GlitterComponent);
408
+
348
409
  export default Glitter;