When using an application, the smooth movement of objects, pages, modals, and other components improves our UX and encourages users to return. No one wants to use an app that glitches and does not move properly.
Creating animations and object transitions might be a burden for frontend developers since we typically want to focus on writing code for our application and not bother calculating where to place an object or where to move that object when a user hits an event on our application.
Working with UI designers can also be a challenge, especially when expectations are misaligned — for example, when designers expect to see their complex animations recreated as-is. Finding a good tool and package to solve this issue is not so easy, either — but that is exactly why the react-native-reanimated
package was built.
The react-native-reanimated
npm package allows us to easily create both simple and complex, yet smooth and interactive animations. It might not be intuitive to use at first, but as we practice with the examples in this article, we should get a good idea of how to use the package.
In this article, we will cover:
Before we continue, I’m assuming you already have a knowledge of React Native and how it works. Also, React Native Reanimated v3 only supports React Native v0.64 or newer, so ensure you update or download the latest version of React Native to work with this tutorial.
If you have ever used React Native Reanimated v1, keep in mind that v2 introduced some new concepts and breaking changes. Let’s explore some of these changes now.
The breaking changes in Reanimated 2 include some name changes to keep in mind. For example, the interpolate
method is now interpolateNode
, while the Easing
method is now EasingNode
.
In React Native Reanimated v2, animations are first-class citizens. Animations are written in pure JS in the form of worklets.
Worklets are pieces of JavaScript code that are picked from the main React Native code by the Reanimated Babel plugin. These pieces of code run in a separate thread using a separate JavaScript virtual machine context and are executed synchronously on the UI thread.
To create a worklet, you will explicitly add the worklet
directive on top of your function. See the code below:
function someWorklet(greeting) { 'worklet'; console.log("Hey I'm running on the UI thread"); }
All the functions with the worklet
directive are picked and run in a separate JavaScript thread. You can also run your function in the UI thread by calling or executing the function using runOnUI
like so:
function FirstWorklet(greeting) { 'worklet'; console.log("Hey I'm running on the UI thread"); } function secondFunction() { console.log("Hello World!"); } function onPress() { runOnUI(secondFunction)(); }
In the code above, FirstWorklet
is the only worklet function, so it will be picked by the Reanimated Babel plugin and run in a separate thread. Meanwhile, the other functions will run synchronously in the main JavaScript thread.
In Reanimated, Shared Values are primitive values that are written on the JavaScript side but are used to drive animations on the UI thread.
As we learned while exploring worklets, Reanimated runs our animation in a separate thread using a separate JavaScript virtual machine context. When we create a value in our main thread or in our JavaScript, Shared Values maintains a reference to that mutable value.
Since it is mutable, we can make changes to the Shared Value and see the referenced changes in the UI thread, along with a change in our animation.
See the code below:
import React from 'react'; import {Button, View} from 'react-native'; import {useSharedValue} from 'react-native-reanimated'; function App() { const sharedValue = useSharedValue(0); return ( <View> <Button title="Increase Shared Value" onPress={() => (sharedValue.value += 1)} /> </View> ); }
As we can see in our code above, Shared Value objects serve as references to pieces of shared data that can be accessed using their .value
property. Therefore, to access and modify our shared data, use the .value
property as seen above.
We use the useSharedValue
hooks to create and store data in our Shared Value references.
useAnimatedStyle
HookThe useAnimatedStyle
Hook allows us to create an association between Shared Values and View
properties to animate our View
stylings using the Shared Values.
Let’s look at the code below:
import { useAnimatedStyle } from 'react-native-reanimated'; const Animation = useAnimatedStyle(() => { return { ...style animation code }; });
The useAnimatedStyle
Hook returns our updated animation styling, gets the Shared Value, and then executes the updates and the styles.
Just like the useEffect
Hook, the useAnimatedStyle
Hook accepts a dependency. When the dependency is omitted as we did above, the update is called only when there’s a change in the body.
If the dependency is included, however, the useAnimatedStyle
Hook runs the update whenever there’s a change in the dependency value, like so:
import { useAnimatedStyle } from 'react-native-reanimated'; const Animation = useAnimatedStyle(() => { return { ...style animation code }; }, [ dependency] );
Now that we have explored some of the new concepts introduced in React Native Reanimated v2, we will now use them to create animations in our application with these new concepts.
To use the React Native Reanimated library, we will have to install the library first. Run either of the commands below to install the package:
// yarn yarn add react-native-reanimated // npm npm i react-native-reanimated --save
Next, go into your babel.config.js
file and add the plugin as shown below:
module.exports = { presets: [ ... ], plugins: [ ... 'react-native-reanimated/plugin', ], };
Note that the React Native Reanimated plugin has to be added last.
Next, in your App.js
file, let’s create a simple page with some header and body content:
import React from 'react'; import {View, Button, Text, StyleSheet} from 'react-native'; const App = () => { return ( <View style={styles.parent}> <Text style={styles.header}>React Native Reanimated Tutorial</Text> <View style={styles.box}> <Button title="View more" /> <View style={{marginTop: 20}}> <Text style={styles.textBody}> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. {'\n'} {'\n'} Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. </Text> </View> </View> </View> ); }; export default App; const styles = StyleSheet.create({ parent: { flex: 1, paddingTop: 40, paddingHorizontal: 20, }, header: { fontSize: 24, marginBottom: 20, textAlign: 'center', }, box: { backgroundColor: '#000', borderRadius: 15, padding: 20, }, textBody: { fontSize: 20, marginBottom: 20, }, });
The goal is to create animated dropdown text that will display when we click a button:
Let’s achieve our desired animation with the useAnimatedStyle
Hook and Shared Values, as shown below:
import React, {useState} from 'react'; import {View, Button, Text, StyleSheet} from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; const App = () => { const boxHeight = useSharedValue(60); const [maxLines, setMaxLines] = useState(2); const truncatedAnimation = useAnimatedStyle(() => { return { height: withTiming(boxHeight.value, {duration: 1000}), }; }, []); function showText() { setTimeout(() => { maxLines === 2 ? setMaxLines(0) : setMaxLines(2); }, 400); boxHeight.value === 60 ? (boxHeight.value = 150) : (boxHeight.value = 60); } return ( <View style={styles.parent}> <Text style={styles.header}>React Native Reanimated Tutorial</Text> <View style={styles.box}> <Button title={maxLines === 2 ? 'View more' : 'Close Dropdown'} onPress={showText} /> <Animated.View style={[{marginTop: 20}, truncatedAnimation]}> <Text style={styles.textBody} numberOfLines={maxLines}> Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. {'\n'} {'\n'} Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur magnam necessitatibus dolores qui sunt? Mollitia nostrum placeat esse commodi modi quaerat, et alias minima, eligendi ipsa perspiciatis, totam quod dolorum. </Text> </Animated.View> </View> </View> ); }; export default App; const styles = StyleSheet.create({ parent: { flex: 1, paddingTop: 40, paddingHorizontal: 20, }, header: { fontSize: 24, marginBottom: 20, textAlign: 'center', }, box: { backgroundColor: '#000', borderRadius: 15, padding: 20, }, textBody: { fontSize: 20, marginBottom: 20, }, });
In our code above, we first imported our react-native-reanimated
package. Next, we used the Animated
options for our View
— which we imported from react-native-reanimated
— to wrap the view that we want to animate.
After that, we set our box’s initial height to 60
using useSharedValue
. We also have a maxLines
state that we’re setting to 2
. The maxLines
state determines the number of lines to which we are truncating our text, which in this case is a maximum of 2
lines.
The showText
function checks if the height is 60
when the button is clicked, and if so, it increases the height to 150
. If the height is already 150
, it will decrease to 60
when the button is clicked instead.
We are also setting the maxLines
state to 0
, meaning we want the full text to be shown when we click on our “View more” button.
With the useAnimatedStyle
Hook, we created our animation association between our shared value and the View
that we’re animating.
You can see the result below:
React Native Reanimated v3 does not introduce any breaking changes, as was the case in v2. Hence, every code written in v2 will work fine in v3. However, an additional feature was introduced, which we will look at extensively. This feature is called Shared Element Transitions.
This new feature introduced in v3 is a transition feature that allows you to animate views between navigation screens. sharedTransitionTag
is the attribute that allows us to animate our screens between navigations.
To create a shared transition animation between screens, simply assign the same sharedTransitionTag
to both components. When you navigate between screens, the shared transition animation will automatically play.
To see how it works, we will be animating an image screen.
First, create four files — ImageDescription.js
, Home.js
, SharedElements.js
, and our root file, App.js
. Copy and paste the relevant code for each file as shown below.
In your App.js
file:
import * as React from 'react'; import {NavigationContainer} from '@react-navigation/native'; import {createNativeStackNavigator} from '@react-navigation/native-stack'; import Home from './src/Home'; import ImageDescription from './src/ImageDescription'; const Stack = createNativeStackNavigator(); function App() { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={Home} options={{ title: 'Posts', headerTintColor: '#000', headerTitleAlign: 'center', headerTitleStyle: { fontWeight: 'bold', }, }} /> <Stack.Screen name="ImageDescription" component={ImageDescription} options={{ title: 'Image', headerTintColor: '#000', headerTitleAlign: 'center', headerTitleStyle: { fontWeight: 'bold', }, }} /> </Stack.Navigator> </NavigationContainer> ); } export default App;
The App.js
file houses our navigation components. We have two screens — the Home
and ImageDescription
screens. We are also customizing our navigation header here to our preferred stylings.
Next, in your Home.js
file:
import React from 'react'; import { Image, StyleSheet, Dimensions, SafeAreaView, TouchableOpacity, } from 'react-native'; import Animated from 'react-native-reanimated'; import {sharedElementTransition} from './SharedElements'; import Img1 from '../assets/image1.jpg'; import Img2 from '../assets/image2.jpg'; import Img3 from '../assets/image3.jpg'; import Img4 from '../assets/image4.jpg'; const imageArray = [ { id: 1, img: Img1, description: `Image 1. To create a shared transition animation between two components on different screens, simply assign the same sharedTransitionTag to both components. When you navigate between screens, the shared transition animation will automatically play. The shared transition feature works by searching for two components that have been registered with the same sharedTransitionTag. If you want to use more than one shared view on the same screen, be sure to assign a unique shared tag to each component.`, }, { id: 2, img: Img2, description: `Image 2. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 3, img: Img3, description: `Image 3. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 4, img: Img4, description: `Image 4. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 5, img: Img3, description: `Image 5. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 8, img: Img2, description: `Image 8. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 7, img: Img1, description: `Image 7. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, { id: 6, img: Img4, description: `Image 6. "Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit..." "There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain..." `, }, ]; export default function Home({navigation}) { return ( <SafeAreaView style={styles.container}> {imageArray.map(image => ( <TouchableOpacity key={image.id} onPress={() => navigation.navigate('ImageDescription', { image: image, }) }> <Animated.View style={styles.imageContainer} sharedTransitionTag="animateImageTag" sharedTransitionStyle={sharedElementTransition}> <Image source={image.img} style={styles.image} /> </Animated.View> </TouchableOpacity> ))} </SafeAreaView> ); } const windowWidth = Dimensions.get('window').width; const styles = StyleSheet.create({ container: { flex: 1, flexWrap: 'wrap', flexDirection: 'row', alignItems: 'center', }, imageContainer: { height: 140, width: windowWidth / 3, }, image: { flex: 1, resizeMode: 'cover', width: '100%', }, });
In the Home
component, we have some images that we are mapping and displaying on our screen, like so:
When an image is clicked, it navigates the user to another screen — the ImageDescription
component — that shows the image clicked, a header title, and a description underneath the image, like so:
To set this component up, copy the below into your ImageDescription.js
file:
import React from 'react'; import Animated from 'react-native-reanimated'; import {sharedElementTransition} from './SharedElements'; import {View, Text, StyleSheet, Image, Dimensions} from 'react-native'; export default function ImageDescription({route}) { const {image} = route.params; return ( <View style={styles.container}> <Text style={styles.title}>Shared Elements Transition</Text> <Animated.View style={styles.imageContainer} sharedTransitionTag="animateImageTag" sharedTransitionStyle={sharedElementTransition}> <Image source={image.img} style={styles.image} /> </Animated.View> <Text style={styles.description}>{image.description}</Text> </View> ); } const windowWidth = Dimensions.get('window').width; const styles = StyleSheet.create({ container: { flex: 1, }, title: { fontSize: 24, fontWeight: 'bold', color: '#000', marginHorizontal: 10, marginVertical: 5, }, imageContainer: { height: 500, width: windowWidth, }, image: { height: '100%', width: '100%', }, description: { fontSize: 20, color: '#000', marginHorizontal: 10, marginVertical: 5, }, });
Next, we’ll introduce our Animated View from react-native-reanimated
. The Animated View is wrapped around the section you want to animate and in our case, the images. Animated.View
takes some options like the sharedTransitionTag
and the sharedTransitionStyle
.
Add the below to your SharedElements.js
file:
import {SharedTransition, withSpring} from 'react-native-reanimated'; const CONFIG = { mass: 1, stiffness: 100, damping: 200, }; export const sharedElementTransition = SharedTransition.custom(values => { 'worklet'; return { height: withSpring(values.currentHeight, CONFIG), width: withSpring(values.currentWidth, CONFIG), originX: withSpring(values.currentOriginX, CONFIG), originY: withSpring(values.currentOriginY, CONFIG), }; });
In the code above, the sharedTransitionTag
lets Reanimated detect the components to animate. So, components with the same sharedTransitionTag
share the animation between them.
Meanwhile, sharedTransitionStyle
allows you to write custom stylings for the height
, width
, originY
, and originX
of the animated components.
The complete app will look like this:
We have learned about changes introduced in React Native Reanimated v2 and v3, the core features, and how to use these core features. We have also seen how to create smooth transitions and animations using the concepts we have learned in the article.
The Share Elements Transition feature is still experimental, which means that a more stable version is being worked on. In the meantime, you can use it and see how it works, although you may not want to use it in production yet.
If you followed the tutorial and examples, you should be able to create even more complex and interactive animations with the same concepts. To explore other ways to animate your next project, see our tutorials on Anime.js and Framer Motion.
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.