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 +24 -0
- package/lib/module/index.js +112 -74
- package/lib/typescript/src/index.d.ts +5 -1
- package/package.json +1 -1
- package/src/index.tsx +178 -100
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:
|
package/lib/module/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
83
|
-
return
|
|
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
|
|
99
|
-
const
|
|
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
|
|
117
|
-
const getTransformOriginOffset = () => {
|
|
160
|
+
const transformOriginOffset = useMemo(() => {
|
|
118
161
|
if (mode === 'normal' || position === 'center') {
|
|
119
162
|
return 0;
|
|
120
163
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
194
|
+
scaleY: scaleY,
|
|
145
195
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
155
|
-
return
|
|
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 =
|
|
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
|
|
175
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
};
|
|
269
|
+
const layerCount = useMemo(
|
|
270
|
+
() => Math.max(11, Math.round(shimmerWidth / 3)),
|
|
271
|
+
[shimmerWidth]
|
|
272
|
+
);
|
|
209
273
|
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
274
|
+
const layerWidth = useMemo(
|
|
275
|
+
() => shimmerWidth / layerCount,
|
|
276
|
+
[shimmerWidth, layerCount]
|
|
277
|
+
);
|
|
213
278
|
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
const horizontalOpacities = useMemo(
|
|
280
|
+
() => generateGlitterOpacities(layerCount, 1),
|
|
281
|
+
[layerCount]
|
|
282
|
+
);
|
|
217
283
|
|
|
218
|
-
const shimmerLayers =
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
{(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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;
|