Updated April 21, 2021
How to use Reanimated 2 (a beginners guide)
Reanimated 2 is a hyper powerful library that all the cool kids use but has always been a bit of a blackbox to me... I saw the power of it but was overwhelmed by the API.
Today I want to share with you what I've learned and we'll create our first Reanimated 2 animation together - an image that changes sizes.
Let's start with an app that renders a 100x100 image. All standard React Native.
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
export default () => {
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
alert('todo!');
}}
>
<Image
source={require('./rns.png')}
resizeMode="contain"
style={{ width: 100, height: 100 }}
/>
</TouchableOpacity>
</View>
);
};
We'll be animating the size of the image. That means that we need to store the value to be animated somewhere.
Reanimated 2 has a concept of shared values. These are values that can be shared across different threads (think JavaScript thread and UI thread) thus the name of shared value.
It's where you'll want to store your "animateable" data - in this case the random number that we'll use to drive the size of the image.
useSharedValue
returns a value - similar to using .current
on a useRef
hook - that means that we can't just access the result directly. We need to access the .value
property of the returned data.
Also note in the following code that I've replaced the Image
component with an Animated.Image
. This is in preparation for when the randomNumber
value will change.
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, { useSharedValue } from 'react-native-reanimated';
export default () => {
const randomNumber = useSharedValue(100);
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
alert('todo!');
}}
>
<Animated.Image
source={require('./rns.png')}
resizeMode="contain"
style={{ width: randomNumber.value, height: randomNumber.value }}
/>
</TouchableOpacity>
</View>
);
};
Now let's go ahead and change that randomNumber
. Make sure you use .value
when accessingly the data.
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, { useSharedValue } from 'react-native-reanimated';
export default () => {
const randomNumber = useSharedValue(100);
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
randomNumber.value = Math.random() * 350;
}}
>
<Animated.Image
source={require('./rns.png')}
resizeMode="contain"
style={{ width: randomNumber.value, height: randomNumber.value }}
/>
</TouchableOpacity>
</View>
);
};
But when we press the image nothing is happening... why?
It's not changing because, unlike when calling setState
, it doesn't cause a re-render to happen (which is good) we're just changing a value.
What we need to do is spawn a worklet that can adjust to changes in randomNumber
.
This worklet will run on the UI thread (rather than the JavaScript thread) allowing us to see a smooth native animation.
To spawn this worklet we're going to use the useAnimatedStyle
hook. This hook will return a style object that updates with randomNumber
. This style object can then be applied to our Animated.Image
.
useAnimateStyle
is similar to useEffect
but actually returns a value (the style object).
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
export default () => {
const randomNumber = useSharedValue(100);
const style = useAnimatedStyle(() => {
return { width: randomNumber.value, height: randomNumber.value };
});
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
randomNumber.value = Math.random() * 350;
}}
>
<Animated.Image
source={require('./rns.png')}
resizeMode="contain"
style={style}
/>
</TouchableOpacity>
</View>
);
};
Now when we press the image the size of it changes (granted it's not animated - we'll get to that).
The final piece of (this) puzzle is to actually animate the values. We've created all the necessary relationships between data and have things updating where they need to be - now we need to choose the animation to use.
Today we'll make "springy".
By importing withSpring
we can tell Reanimated 2 how we want that data to change.
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
export default () => {
const randomNumber = useSharedValue(100);
const style = useAnimatedStyle(() => {
return {
width: randomNumber.value,
height: randomNumber.value,
};
});
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
randomNumber.value = withSpring(Math.random() * 350);
}}
>
<Animated.Image
source={require('./rns.png')}
resizeMode="contain"
style={style}
/>
</TouchableOpacity>
</View>
);
};
Alternatively we could use withSpring
inside of useAnimatedStyle
to customize how each property changes.
import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
export default () => {
const randomNumber = useSharedValue(100);
const style = useAnimatedStyle(() => {
return {
width: withSpring(randomNumber.value),
height: withSpring(randomNumber.value, { stiffness: 10 }),
};
});
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
backgroundColor: '#7CA1B4',
}}
>
<TouchableOpacity
onPress={() => {
randomNumber.value = Math.random() * 350;
}}
>
<Animated.Image
source={require('./rns.png')}
resizeMode="contain"
style={style}
/>
</TouchableOpacity>
</View>
);
};
And that's on me trying to figure out the moving parts of Reanimated 2. To me it's a pretty complex library to first learn but the power of it is incredible.
If you have any comments/corrections/questions please reach out on Twitter.