So this started when i wanted to create placeholder components using SVG.. Here’s the complete code:
1// Placeholder.tsx
2import React, { useEffect } from 'react'
3import { Animated, View } from 'react-native'
4import Svg, {
5 SvgProps,
6 Circle,
7 CircleProps,
8 Rect,
9 RectProps,
10 CommonPathProps
11} from 'react-native-svg'
12
13type Props = Readonly<{
14 loading?: boolean
15 shape?: 'circle' | 'rect'
16 size: number
17 height: number
18 width: number
19}>
20
21// set a value to be animated
22const currentColor = new Animated.Value(0)
23
24// interpolate color value
25const changeColor = currentColor.interpolate({
26 inputRange: [0, 1, 2], // the values that the animation will transition from
27 outputRange: ['gainsboro', 'whitesmoke', 'gainsboro'] // values that are animating
28})
29
30// define the looping animation
31const animateColor = ()=> {
32 Animated.loop(
33 Animated.sequence([
34 Animated.timing(currentColor, {
35 toValue: 2, // the value in interpolated output range that you want to go to
36 duration: 2000 // ms
37 }),
38 ])
39 ).start()
40 }
41
42export default function Placeholder({
43 loading = true,
44 size = 50,
45 height = 50,
46 width = 50,
47 radius = 8,
48 shape = 'rect',
49 fill = 'gainsboro',
50 ...rest
51}: Props &
52 SvgProps &
53 CircleProps &
54 RectProps &
55 CommonPathProps &
56 any) {
57
58 useEffect(() => {
59 // start the animation to change background color
60 animateColor()
61 })
62
63 const AnimatedSvg = Animated.createAnimatedComponent(Svg)
64
65 return (
66 <View
67 loading={loading}
68 style={{margin: 8}}
69 {...rest}
70 >
71 {shape === 'circle' && (
72 <AnimatedSvg
73 size={size}
74 height={size}
75 width={size}
76 viewBox={`0 0 ${size * 2} ${size * 2}`}
77 fill={loading ? changeColor : fill}
78 >
79 <Circle
80 cx={size}
81 cy={size}
82 r={size}
83 />
84 </AnimatedSvg>
85 )}
86
87 {shape === 'rect' && (
88 <AnimatedSvg
89 height={height}
90 width={width}
91 viewBox={`0 0 ${width} ${height}`}
92 fill={loading ? changeColor : fill}
93 >
94 <Rect
95 x="0"
96 y="0"
97 rx={radius}
98 width={width}
99 height={height}
100 />
101 </AnimatedSvg>
102 )}
103 </View>
104 )
105}
Referencing the component:
1// App.tsx
2import React from 'react';
3import { StyleSheet, View } from 'react-native';
4import Placeholder from './Placeholder'
5
6export default function App() {
7
8 return (
9 <View style={styles.container}>
10 <Placeholder />
11 <Placeholder loading={false}/>
12 <Placeholder shape='circle' size={150}/>
13 <Placeholder shape='rect' width={200} />
14 <Placeholder loading={false} shape='circle' size={100}/>
15 </View>
16 );
17}
18
19const styles = StyleSheet.create({
20 container: {
21 flex: 1,
22 alignItems: 'center',
23 justifyContent: 'center',
24 },
25});
react-native-svg
for thatAnimated
. So made the SVG a custom Animated component using createAnimatedComponent()
1import { Path } from 'react-native-svg'
2const AnimatedPath = Animated.createAnimatedComponent(Path)
The prop types are there because i’m using Typescript
The <Placeholder>
component can take
shape
prop, the two options are circle
or rect
size
for circle
height
and width
for rect
loading: boolean
to decide whether to animate the background color or notrx
, height
, width
, loading
, size
, shape
and fill
)I originally had height
and width
for circle
as well, with size
as a complimentary value. But i ended up getting rid of those since it is a circle.. Common usage will always have the same height
and width