Skip to content

Commit 135a46a

Browse files
committed
feat: added more customizability
1 parent 39fee7f commit 135a46a

File tree

5 files changed

+236
-52
lines changed

5 files changed

+236
-52
lines changed

example/src/App.tsx

Lines changed: 144 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,102 @@
11
import { Shimmer, ShimmerProvider } from 'react-native-fast-shimmer';
2-
import { View, StyleSheet, SafeAreaView } from 'react-native';
2+
import { View, StyleSheet, SafeAreaView, Text } from 'react-native';
3+
import { Easing } from 'react-native-reanimated';
34

45
export default function App() {
56
return (
67
<SafeAreaView style={styles.container}>
7-
<ShimmerProvider duration={1000}>
8-
<View style={styles.shimmer1}>
9-
<Shimmer style={styles.shimmerMain} />
8+
<View style={styles.configsContainer}>
9+
<View style={styles.configContainer}>
10+
<Text>Shared config</Text>
11+
<ShimmerProvider duration={3000}>
12+
<View style={styles.sharedConfigWrapper}>
13+
<View style={styles.shimmer}>
14+
<Shimmer style={styles.shimmerMain} />
15+
</View>
16+
<View style={styles.shimmer}>
17+
<Shimmer style={styles.shimmerMain} />
18+
</View>
19+
<View style={styles.shimmer}>
20+
<Shimmer style={styles.shimmerMain} />
21+
</View>
22+
</View>
23+
</ShimmerProvider>
1024
</View>
11-
<View style={styles.shimmer2}>
12-
<Shimmer style={styles.shimmerMain} />
25+
<View style={styles.configContainer}>
26+
<Text>Individual override</Text>
27+
<ShimmerProvider duration={3000}>
28+
<View style={styles.sharedConfigWrapper}>
29+
<View style={styles.shimmer}>
30+
<Shimmer
31+
gradientEnd={{ x: 0, y: 0 }}
32+
gradientStart={{ x: 0.2, y: 0 }}
33+
style={styles.shimmerMain}
34+
/>
35+
</View>
36+
<View style={styles.shimmer}>
37+
<Shimmer
38+
linearGradients={['#FF000000', '#0000FFFF', '#FF000000']}
39+
style={styles.shimmerMain}
40+
/>
41+
</View>
42+
<View style={styles.shimmer}>
43+
<Shimmer speed={2} style={styles.shimmerMain} />
44+
</View>
45+
</View>
46+
</ShimmerProvider>
1347
</View>
14-
<View style={styles.shimmer3}>
15-
<Shimmer style={styles.shimmerMain} />
16-
</View>
17-
</ShimmerProvider>
48+
</View>
49+
<View style={styles.longShimmers}>
50+
<ShimmerProvider duration={3000}>
51+
<View style={styles.easingShimmerContainer}>
52+
<Text>Linear</Text>
53+
<View style={[styles.shimmerMain, styles.longShimmer]}>
54+
<Shimmer easing={Easing.linear} />
55+
</View>
56+
</View>
57+
58+
<View style={styles.easingShimmerContainer}>
59+
<Text>Ease</Text>
60+
<View style={[styles.shimmerMain, styles.longShimmer]}>
61+
<Shimmer easing={Easing.ease} />
62+
</View>
63+
</View>
64+
<View style={styles.easingShimmerContainer}>
65+
<Text>Cubic</Text>
66+
<View style={[styles.shimmerMain, styles.longShimmer]}>
67+
<Shimmer easing={Easing.cubic} />
68+
</View>
69+
</View>
70+
<View style={styles.easingShimmerContainer}>
71+
<Text>Circle</Text>
72+
<View style={[styles.shimmerMain, styles.longShimmer]}>
73+
<Shimmer easing={Easing.circle} />
74+
</View>
75+
</View>
76+
<View style={styles.avatarShimmers}>
77+
<View style={styles.avatarsContainer}>
78+
<View style={styles.avatarCircle}>
79+
<Shimmer easing={Easing.ease} />
80+
</View>
81+
<View style={styles.avatarCircle}>
82+
<Shimmer easing={Easing.ease} />
83+
</View>
84+
<View style={styles.avatarCircle}>
85+
<Shimmer easing={Easing.ease} />
86+
</View>
87+
<View style={styles.avatarCircle}>
88+
<Shimmer easing={Easing.ease} />
89+
</View>
90+
<View style={styles.avatarCircle}>
91+
<Shimmer easing={Easing.ease} />
92+
</View>
93+
<View style={styles.avatarCircle}>
94+
<Shimmer easing={Easing.ease} />
95+
</View>
96+
</View>
97+
</View>
98+
</ShimmerProvider>
99+
</View>
18100
</SafeAreaView>
19101
);
20102
}
@@ -26,25 +108,65 @@ const styles = StyleSheet.create({
26108
justifyContent: 'flex-start',
27109
gap: 12,
28110
},
29-
shimmer1: {
30-
width: 200,
31-
height: 50,
32-
borderRadius: 10,
33-
backgroundColor: 'gray',
111+
configsContainer: {
112+
display: 'flex',
113+
flexDirection: 'row',
114+
gap: 10,
115+
paddingHorizontal: 20,
34116
},
35-
shimmer2: {
117+
configContainer: {
118+
display: 'flex',
119+
flexDirection: 'column',
120+
gap: 10,
121+
alignItems: 'center',
122+
},
123+
sharedConfigWrapper: {
124+
alignItems: 'center',
125+
justifyContent: 'center',
126+
gap: 12,
127+
},
128+
shimmer: {
36129
width: 200,
37130
height: 50,
38-
borderRadius: 10,
39-
backgroundColor: 'gray',
40131
},
41-
shimmer3: {
42-
width: 200,
132+
longShimmer: {
133+
width: '100%',
43134
height: 50,
44-
borderRadius: 10,
45-
backgroundColor: 'gray',
46135
},
47136
shimmerMain: {
137+
backgroundColor: 'gray',
48138
borderRadius: 10,
49139
},
140+
avatarsContainer: {
141+
display: 'flex',
142+
flexDirection: 'row',
143+
flexWrap: 'wrap',
144+
gap: 10,
145+
width: '100%',
146+
justifyContent: 'space-between',
147+
},
148+
avatarCircle: {
149+
width: 100,
150+
height: 100,
151+
backgroundColor: 'gray',
152+
borderRadius: 50,
153+
},
154+
easingShimmerContainer: {
155+
display: 'flex',
156+
flexDirection: 'column',
157+
justifyContent: 'center',
158+
alignItems: 'center',
159+
gap: 10,
160+
width: '100%',
161+
},
162+
longShimmers: {
163+
width: '100%',
164+
alignItems: 'center',
165+
paddingHorizontal: 10,
166+
},
167+
avatarShimmers: {
168+
width: '100%',
169+
height: 300,
170+
marginTop: 20,
171+
},
50172
});

src/LinearGradient.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import React from 'react';
22
import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
33
import type { ViewStyle, StyleProp } from 'react-native';
4+
import { extractOpacityFromColor } from './helpers';
45

5-
interface Props {
6+
export interface GradientConfig {
67
colors: string[];
78
start?: { x: number; y: number };
89
end?: { x: number; y: number };
10+
}
11+
12+
interface Props extends GradientConfig {
913
style?: StyleProp<ViewStyle>;
1014
children?: React.ReactNode;
1115
}
@@ -16,7 +20,7 @@ export const MyLinearGradient = ({
1620
end = { x: 0, y: 1 },
1721
style,
1822
}: Props) => {
19-
const gradientId = 'grad';
23+
const gradientId = colors.join('-');
2024

2125
return (
2226
<Svg style={style}>
@@ -33,7 +37,7 @@ export const MyLinearGradient = ({
3337
key={i}
3438
offset={`${(i / (colors.length - 1)) * 100}%`}
3539
stopColor={color === 'transparent' ? 'white' : color}
36-
stopOpacity={color === 'transparent' ? 0 : 1}
40+
stopOpacity={extractOpacityFromColor(color)}
3741
/>
3842
))}
3943
</LinearGradient>

src/Shimmer.tsx

Lines changed: 50 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,45 @@
1-
import React, { useContext, useEffect, useState } from 'react';
2-
import { View, StyleSheet, type ViewStyle } from 'react-native';
3-
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
1+
import React, { useCallback, useContext, useEffect, useState } from 'react';
2+
import {
3+
View,
4+
StyleSheet,
5+
type ViewStyle,
6+
Dimensions,
7+
type LayoutChangeEvent,
8+
} from 'react-native';
9+
import Animated, {
10+
Easing,
11+
useAnimatedStyle,
12+
type EasingFunction,
13+
} from 'react-native-reanimated';
414
import { ShimmerContext } from './ShimmerContext';
515
import { MyLinearGradient } from './LinearGradient';
616

7-
const DEFAULT_LINEAR_GRADIENTS = ['transparent', '#FFFFFF30', 'transparent'];
17+
const DEFAULT_LINEAR_GRADIENTS = ['transparent', '#FFFFFFFF', 'transparent'];
818
const DEFAULT_GRADIENT_START = { x: 0, y: 0.5 };
919
const DEFAULT_GRADIENT_END = { x: 1, y: 0.5 };
20+
const SCREEN_WIDTH = Dimensions.get('window').width;
1021

11-
interface Props {
22+
interface ShimmerProps {
1223
style?: ViewStyle | ViewStyle[];
1324
linearGradients?: string[];
1425
gradientStart?: { x: number; y: number };
1526
gradientEnd?: { x: number; y: number };
27+
easing?: EasingFunction;
28+
speed?: number;
1629
}
1730

1831
export const Shimmer = ({
1932
style,
2033
linearGradients = DEFAULT_LINEAR_GRADIENTS,
2134
gradientStart = DEFAULT_GRADIENT_START,
2235
gradientEnd = DEFAULT_GRADIENT_END,
23-
}: Props): React.ReactNode => {
36+
easing = Easing.linear,
37+
speed = 1,
38+
}: ShimmerProps): React.ReactNode => {
2439
const shimmer = useContext(ShimmerContext);
25-
const shimmerRef = React.useRef<View>(null);
40+
2641
const [offset, setOffset] = useState(0);
27-
const progress = shimmer?.progress;
42+
const [componentWidth, setComponentWidth] = useState(0);
2843

2944
useEffect(() => {
3045
shimmer?.increaseActiveShimmers();
@@ -33,27 +48,42 @@ export const Shimmer = ({
3348
};
3449
}, [shimmer]);
3550

36-
const measure = () => {
37-
if (shimmerRef.current) {
38-
shimmerRef.current.measureInWindow((x) => {
39-
setOffset(x);
40-
});
41-
}
42-
};
51+
const measure = useCallback(
52+
(event: LayoutChangeEvent) => {
53+
if (componentWidth === 0) {
54+
event.target.measureInWindow((x, _y, width) => {
55+
setComponentWidth(width);
56+
setOffset(x);
57+
});
58+
}
59+
},
60+
[componentWidth]
61+
);
4362

4463
const gradientStyle = useAnimatedStyle(() => {
64+
const localProgress = ((shimmer?.progress?.value ?? 0) * speed) % 1;
65+
const easedProgress = easing(localProgress);
66+
67+
const remappedRange =
68+
-(componentWidth + offset) +
69+
((SCREEN_WIDTH + componentWidth) * easedProgress) / 1;
70+
4571
return {
46-
transform: [{ translateX: (progress?.value ?? 0) - offset }],
72+
transform: [
73+
{
74+
translateX: remappedRange,
75+
},
76+
],
4777
};
4878
});
4979

5080
return (
51-
<View ref={shimmerRef} onLayout={measure} style={[styles.container, style]}>
81+
<View onLayout={measure} style={[styles.container, style]}>
5282
<Animated.View style={[gradientStyle, styles.gradientWrapper]}>
5383
<MyLinearGradient
54-
colors={linearGradients}
55-
start={gradientStart}
56-
end={gradientEnd}
84+
colors={shimmer?.gradientConfig?.colors ?? linearGradients}
85+
start={shimmer?.gradientConfig?.start ?? gradientStart}
86+
end={shimmer?.gradientConfig?.end ?? gradientEnd}
5787
style={StyleSheet.absoluteFill}
5888
/>
5989
</Animated.View>

0 commit comments

Comments
 (0)