React Native Reanimated is a powerful, intuitive library that allows you to create smooth animations and interactions for iOS and Android applications. Although there are many versatile and performant animation libraries for React Native, including the built-in Animated
API, we’ll examine Reanimated in-depth to discover why it’s the superior choice in my opinion.
Let’s get started!
Reanimated’s core strength lies in its ability to improve the performance and responsiveness of React Native applications, giving animations a smooth finish that can be achieved only through instantaneous code execution.
To understand how Reanimated works, we first need to understand how React Native executes code.
React Native has three separate threads that allow the execution of time-intensive JavaScript code without affecting the UI’s responsiveness. The UI thread is the native thread. It runs either Swift or Objective C for iOS and either Java or Kotlin for Android. The application’s UI is manipulated solely on the UI thread.
The JavaScript thread is responsible for the render logic, like which views are displayed and in what manner. It executes JavaScript separately via a JavaScript engine. Finally, the bridge enables asynchronous communication between the UI and the JavaScript thread. To avoid slowing down performance, it should only be used to transfer small amounts of data.
These interactions start on the JavaScript thread and must be reflected on the main thread, potentially causing problems when dealing with event-driven interactions.
Animated
APIBecause communication between the UI and the JavaScript thread is asynchronous, the Animated
API delays updates by at least one frame, which lasts approximately 16.67ms. The delay can last even longer when the JavaScript thread is also executing other processes like running React diffing and processing network requests.
Known as dropping a frame, these delays can harm your UX and make animations appear clunky. Reanimated solves this problem by removing animation and event handling logic from the JavaScript thread and running it directly on the UI thread.
Additionally, Reanimated defines worklets, which are tiny chunks of JavaScript code that can be moved to a separate JavaScript virtual machine and executed synchronously on the UI thread. Worklets cause animations to run as soon as they are triggered, creating a more satisfying UX.
Shared Values, which are similar to stateful data in a regular React application, store dynamic data that you can animate in Reanimated. However, instead of sharing data between components like you would with stateful data, Shared Values share data between the UI thread and the JavaScript thread.
When data inside of these Share Values is updated, the Reanimated library registers the change and executes an animation, similar to how React re-renders a component when state is updated.
Creating a Shared Value looks quite similar to creating a piece of state with the useState
Hook; simply use the useSharedValue
Hook instead.
Let’s say we want to create a box that changes height when a button is pressed. We’ll define a boxHeight
variable as a Shared Value so that we can share it between the UI thread and the JavaScript thread during the animation:
const boxHeight = useSharedValue(150);
In the example above, we created a Shared Value called boxHeight
and set its initial value to 150
. To access the boxHeight
value later in your code, you must reference boxHeight.value
instead of just boxHeight
.
The useSharedValue
Hook returns an object, and the initial value is saved to the .value
property of the object. To update the Shared Value, which is typically done after some predetermined user input, simply update the .value
property within a function. Reanimated will register the change:
function toggleHeight() { boxHeight.value === 450 ? boxHeight.value = 150 : boxHeight.value = 450; }
Now, let’s explore how we can use these Shared Values in Reanimated’s worklets.
Worklets are simple functions that allow us to execute JavaScript code synchronously on the UI thread. Typically, worklets return a React component’s style property. A worklet is triggered by any change to the Shared Value it references.
To declare a worklet, we can use the worklet
directive in the beginning of the function definition. In the code block below, we are declaring the boxAnimation
function using the worklet
directive, indicating that the function is a worklet. We are returning an updated height
property using the Shared Value boxHeight
:
const boxAnimation = () => { 'worklet'; return { height: withTiming(boxHeight.value, {duration: 750}) }; };
More commonly, we can use the useAnimatedStyle
Hook to declare a worklet, which allows us to pass in a callback function as an argument. Now, the callback function will be treated as a worklet:
const boxAnimation = useAnimatedStyle(() => { return { height: withTiming(boxHeight.value, {duration: 750}) }; });
Of the two methods for declaring a worklet, I recommend using the useAnimatedStyle
Hook. To use the worklet
directive method, we must add a secondary function that calls the runOnUI
method and passes the boxAnimation
function as a parameter. On the other hand, the useAnimatedStyle
hook abstracts that secondary function away, automatically running the callback passed to the useAnimatedStyle
Hook on the UI thread.
In both of our examples, whenever the value of boxHeight.value
is updated, the worklet will trigger an animation showing the box either expanding or contracting vertically.
withTiming
In our examples above, we used the withTiming
utility method, which allows us to create a simple animation that gradually transitions from its start point to its endpoint, giving us the ability to control the duration and acceleration of the transition.
withTiming
takes two parameters. The first, which is required, is the Shared Value that will update. In our case, it is boxHeight
.
The second optional parameter is an object with two properties. duration
controls the length of time the animation takes, and easing
controls the animation’s acceleration and deceleration. The default values are 300ms for duration
and in-out quad easing
for easing
.
withSpring
The withSpring
method is similar to withTiming
, however, it creates a different animation effect in which the element passes its endpoint before settling into its ending position.
withSpring
only has one required parameter, which is the Shared Value to be updated. It also has six optional parameters listed below. However, the default values will suffice for most use cases:
damping
: 10mass
: 1stiffness
: 100overshootClamping
: falserestDisplacementThreshold
: 0.01restSpeedThreshold
: 2useAnimatedStyle
HookIn our previous example, the useAnimatedStyle
Hook creates a worklet that links the Shared Value boxHeight
with a component that uses boxHeight.value
in its style properties. When giving React components properties, we must use the component version that we can animate.
For example, instead of using a <View />
tag, we should use an <Animated.View />
tag. All children of <Animated.View />
will be subject to the animation applied to the parent.
When setting styles for the component, be sure to pass the styles as an array. The first element is an object containing all of the styles you would typically use, including height
. The second element is the worklet that was defined earlier.
Now, let’s combine all of the Reanimated tools that we’ve covered to create a simple grey box that expands and contracts when we press a button:
// import statements where we add the functionality to use hooks and methods from Reanimated import React from 'react'; import { View, Button } from 'react-native'; import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; export default function ExpandingTextBox() { // creating shared value via useSharedValue const boxHeight = useSharedValue(150); // creating worklet via useAnimatedStyle, and incorporating the withTiming method const boxAnimation = useAnimatedStyle(() => { return { height: withTiming(boxHeight.value, {duration: 750}) } }); // function that toggles the value of boxHeight so it can expand and contract function toggleHeight() { boxHeight.value === 450 ? boxHeight.value = 150 : boxHeight.value = 450; }; // styles to use on our grey box const styles = { box: { width: 400, height: 150, backgroundColor: 'grey', borderRadius: 15, margin: 100, padding: 20, display: 'flex' } }; return ( <View style={styles.app}> {/* Animated.View component, with the typical styles contained in styles.box, and the worklet "boxHeight" that updates the height property alongside it */} <Animated.View style={[styles.box, boxAnimation]}> {/* button that fires off toggleHeight() function every time it's pressed */} <Button title='More' onPress={() => toggleHeight()} /> </Animated.View> </View> ) };
The output of the code above will look like the animation below:
Let’s add another Shared Value and worklet to control text opacity. We’ll also add some more detail to the styling.
Now, we have a fully functional animated text dropdown!
Reanimated offloads event-driven interactions from the JavaScript thread and instead executes them synchronously on the UI thread. With Reanimated, you no longer have to worry about dropping frames or limiting the workload of your JavaScript thread.
For more information on Reanimated’s methods, hooks, and animations, I recommend checking out the Reanimated documentation.
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 nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare 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.
One Reply to "Deep dive into React Native Reanimated"
That was well explaines, sir! You deserve a cookie!