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 +2 -0
- package/lib/module/index.js +81 -72
- package/lib/typescript/src/index.d.ts +3 -1
- package/package.json +1 -1
- package/src/index.tsx +157 -111
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} />
|
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,49 +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);
|
|
60
|
+
const currentIterationRef = useRef(null);
|
|
56
61
|
const iterationCount = useRef(0);
|
|
57
|
-
const
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
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(
|
|
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
|
-
|
|
107
|
-
return
|
|
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
|
|
123
|
-
const
|
|
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
|
|
141
|
-
const getTransformOriginOffset = () => {
|
|
160
|
+
const transformOriginOffset = useMemo(() => {
|
|
142
161
|
if (mode === 'normal' || position === 'center') {
|
|
143
162
|
return 0;
|
|
144
163
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
|
|
194
|
+
scaleY: scaleY,
|
|
169
195
|
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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<
|
|
123
|
-
|
|
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
|
-
|
|
136
|
-
toValue: 1,
|
|
137
|
-
duration,
|
|
138
|
-
useNativeDriver: true,
|
|
139
|
-
easing: easing ?? defaultEasing,
|
|
140
|
-
});
|
|
156
|
+
onAnimationStart?.();
|
|
141
157
|
|
|
142
|
-
const
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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(
|
|
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
|
-
|
|
182
|
-
return
|
|
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 =
|
|
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
|
|
202
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
};
|
|
269
|
+
const layerCount = useMemo(
|
|
270
|
+
() => Math.max(11, Math.round(shimmerWidth / 3)),
|
|
271
|
+
[shimmerWidth]
|
|
272
|
+
);
|
|
236
273
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
|
|
274
|
+
const layerWidth = useMemo(
|
|
275
|
+
() => shimmerWidth / layerCount,
|
|
276
|
+
[shimmerWidth, layerCount]
|
|
277
|
+
);
|
|
240
278
|
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
279
|
+
const horizontalOpacities = useMemo(
|
|
280
|
+
() => generateGlitterOpacities(layerCount, 1),
|
|
281
|
+
[layerCount]
|
|
282
|
+
);
|
|
244
283
|
|
|
245
|
-
const shimmerLayers =
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
{(
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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;
|