New Reanimated V2 shines React-Native animation ⚡️

New react-native-reanimated v2 API and concept

MJ Studio
MJ Studio

--

Animation in React Native

Had you ever implement animation in the React Native development process? The only way to create animation with React Native core is Animated API.

The Animated API of React Native provides an easy API for animation and it can cover over 90% of use cases. It has duration-based animation or spring or decay animation and APIs for canceling animation or repeat and interpolation. The Animated.Value instance is a reactive observable value and we can generate our appropriate values from it with interpolation. Also, animations in Animated API can be started in an imperative way so we can easily start the animation from our UI event and custom starting timing like in function.

Let me show an example.

The Animated is imported from react-native.

The value has ranged from 0 to 1. And the width variable is interpolated from value and has ranged from 0 to 350. We can use Animated.View for using animated props or style properties in React Native core Animated API.

The animation is working well. But, the React Native core Animated API is working in the JS thread and updates values with a bridge between the JS thread and UI(native) thread of the application. This means that if our JS thread is busy because of intensive computational works or network calls(The JS is a single Thread language), then the fps(frame per second) of the JS thread will be dropped. The frame drop of the application causes animation hitch and eventually a bad user experience.

We can turn on the inspection feature the fps of UI and JS thread with React Native debug menu.

I added a snippet for intensive work in the JS thread.

useEffect(() => {
setInterval(() => {
for (let i = 0; i < 100000000; i++);
}, 0);
}, []);

Ok, let’s see the JS thread frame.

Normally, the idle fps is 60. The 60 fps ensures a user can see smooth animation in our application. But in this case, the JS thread fps is just 38. It causes hitch animation.

The fps of GIF files are the same as 10fps. Can you feel hitch? The above example is a simple one. In real-world production, the intensive works in the JS thread are more, the hitch of animation is more notifiable.

We can turn onuseNativeDriver option in Animated API. This option will send the entire animation information to the UI thread before starting the animation from the JS thread. The animation hitch will disappear because the animation will be processed in the UI thread only.

Animated.timing(value, { toValue: Math.random(), duration: 1000, useNativeDriver: true }).start();

But useNativeDriver option is can be used with restricted properties. If we try to use this option for layout related properties, then the following error will be invoked.

This problem is more problematic in the gesture handling process of React Native.

Reanimated rescues us from frame drop hell in React Native

The Reanimated(react-native-reanimated) is an animation library for React Native using UI thread. The base concept of Reanimated is similar to useNativeDriver in core Animated API. We have to declare Reanimated nodes in the JS thread, then node information is passed to the UI thread before starting animations.

Reanimated V1 and Redash

The node API in Reanimated V1 was a small language for defining our animation behaviors. The arithmetic operations like add, subtract, multiply, divide, etc are defined in the library. The basic concept of API is similar to the core API. We can create Animated.Value for indicating current animation state and can interpolate several values from it and the library provides Animated.View and Animated.createAnimatedComponent.

The basic API usage of Reanimated V1 is a low-level one and missing some features for complex use cases and makes us confused. We should write some boilerplates for declaring node for animations. The react-native-redash library made by William Candillon is a helper library for react-native-reanimated. Currently, the version of redash is 15.x.x and supports both Reanimated V1 and V2. William Candillon is a tutor of React Native animation and has a Youtube channel for tutorials and also tutoring courses.

Anyway, we can declare our nodes for animation more easily like the following.

The animation seems smooth like 60fps even if the fps of the JS thread is just 38. The reason is animation is running on the UI thread natively.

The Animated is imported fromreact-native-reanimated. Also, timing and useValue helper functions are imported from react-native-redash/lib/module/v1. The useCode in Reanimated V1 is a hook for communication between the UI and the JS threads. When trigger state(the JS thread variable) is changed, the nodes in useCode are re-launched.

Problems in Reanimated V1

The Reanimated V1 is useful for many situations and I have used it in many use cases. But there are some uncomfortable edge cases in Reanimated V1 API.

Complex syntax

Basically, the syntax of Reanimated V1 is so complex. Even if I used react-native-redash for declaring nodes, the use cases more complex, the codes are going crazy more. Also, I can’t expect the computations of nodes easily. If you have used Reanimated V1 with react-native-gesture-handler, then you know what I am talking about(gesture states handling). Finally, I had to learn a new small language to just create animation in React Native 🤣.

Can’t run animation with the imperative manner

Even in a simple use case like run animation when the user pressed the button, it is hard to implement. We should create additional Clocks or Value s for handling running animation.

Turbo module and JSI in React Native re-architecture milestone

First of all, I won’t discuss the re-architecture of React Native deeply in this posting. You can check dedicated discussions in Github or well-described postings.

Did you ever hear about the React Native re-architecture project? React Native is evolving rapidly and the legacy core architecture of React Native is having changed with those steps. The following feature is an overview of the React Native legacy architecture.

Taken from this talk by Lorenzo S. https://t.co/wDaXRvLtlA?amp=1

The main problem of legacy architecture is the communication method between the UI and JS threads. It is asynchronized JSON messaging method using the bridge. It is critically harmful for animation, especially in gesture handling.

Taken from this talk by Lorenzo S. https://t.co/wDaXRvLtlA?amp=1

The JSI(JavaScript Interface) is a technic that JavaScript refers directly to C++ instance and calls its the method in a synchronized way. The turbo module is a new concept about React Native native module using JSI. With the turbo module, we can reduce our application initial launch time(splash) because we don’t need to instantiate all native module not be required in launch time. Furthermore, the turbo module makes our app more fastly in the communication process between the UI and the JS threads using JSI.

Introducing Reanimated V2

The Reanimated V2 is almost the first try that uses the turbo module as far as I know. Currently, the latest version of react-native-reanimated is 2.0.0-alpha09 . Yes, it is an alpha release yet. But its API and functions are stabilized and are developed rapidly. Because Reanimated V2 utilizes the turbo module, we must additional installation steps for using this library.

The Reanimated V2 removed all complex API for declaring animations. I think it is simpler than the core Animated API now. It provides some hooks and functions to create animations and those are very intuitive.

One more, the Reanimated team developed a new feature named Worklet.

Worklet

Originally, the Worklet is a web specification about a web-worker(service worker). The Worklet in Reanimated V2 is a secondary JS context that lives in the UI thread. We should understand correctly Worklet for utilizing Reanimated V2. In this library, the Worklet is represented as a function with ‘worklet’; directive at the first line of the function like the following example.

function someWorklet(greeting) {
'worklet';
console.log("Hey I'm running on the UI thread");
}

The Worklet function is transformed with the custom Babel plugin and can be run in the UI thread also. The running context of Worklet is not fixed in the UI thread or the JS thread. It is determined from the caller's perspective. So we should anticipate whether function or Worklet is run in the UI thread or the JS thread in runtime.

The Worklet can capture the functions or variables in the JS thread of the outer scope of Worklet. Yes, it can be considered as the closure of JavaScript. But if the Worklet will be run in the UI thread, then Worklet should copy the values from the JS context and should refer to functions by the way.

So why the Worklet is required in this Reanimated V2? The big reason for that is we can run the codes in a synchronized way. In animation, the codes that are run asynchronized with the bridge are a critical reason for the awkward moving of our components.

SharedValue

Reanimated V2 introduces a concept namedSharedValue with Worklet. The SharedValue is lived in the UI thread context and has a similar concept to Animated.Value in core Animated API.

SharedValue has a reactiveness. When the value ofSharedValue is changed, the Worklets which have dependencies about ShardValue will run block for updating derived values or style or any others.

With SharedValue, we can utilize it with other hooks or animation helper functions. In most hooks or callback function bodies, we don’t need a directive ‘worklet’ manually. That functions are treated as a Worklet automatically.

  • useSharedValue: create SharedValue
  • useDerivedValue: derive another value from a SharedValue(like observable interpolation)
  • useAnimatedStyle: create a style object of React Native with an animated, observed manner.
  • useAnimatedScrollHandler, useAnimatedGestureHandler: I think, the biggest thing that makes me happy after bump Reanimated to V2 is the easy usage of the gesture handlings. We don’t need to calculate animations from the state of the gesture handler anymore. The callback-based API is very simple and intuitive.
  • withTiming, withSpring, withDecay: we should update value the property of SharedValue with these animation helper functions.
  • runOnUI, runOnJS: makes running context of functions. These helpers have some restrictions by the way.

Example with Reanimated V2

Okay, we have explored Reanimated V2 concept and APIs. Let’s change our example with Reanimated V2.

When I pressed the button, the value property of SharedValue(value) is changed with withSpring animation helper function. And, I created animatedStyle view style object with useAnimatedStyle.

Advanced use cases

Recently, I applied Reanimated V2 into my project with migrations from V1. The result was successful. My graph cursor animation and Youtube-like video modal animation based on gesture handling are more smoothly than before.

Conclusion

React Native is a new platform for mobile development compare to other platforms(not newer than Flutter 😅). Not only the animation but also other core features are evolving rapidly and sometimes it feels unstable. I cannot bet on the future of React Native but I am looking forward to the successful adoption of re architecture project of React Native and Reanimated V2 as a good start point for them.

Thank you for reading 🙌

--

--