Intro to SVG Animations with React-Native ReAnimated 2
Written by: Daniel Friyia, Agile Software Engineer, TribalScale
📫 Subscribe to receive our content here.
💬 Have any questions about our development capabilities? Click here to chat with one of our experts!
A few months ago, React Native ReAnimated Version 2 came out and I’ve really enjoyed working with it. Honestly, before this library was released, I was hesitant to write many animations in React Native 😅. It felt like they always came out janky in Android while Flutter seemed to have a clear edge in this domain. From the tinkering I’ve done so far, ReAnimated 2 feels like a game changer.
’s use of the JavaScript Interface (JSI) with the new React-Native architecture has made a world of difference.ReAnimated 2 makes me feel way more free as a developer to express my creativity through animations. It’s always a great feeling when you can take a design and make it come to life by animating it for users to view. I thought I would share a short introduction on how to do SVG animations with this new library so other React-Native developers could get a taste of how it works. I can’t wait to see the new things this library allows us to create 💪! If you get stuck at any point, feel free to check out my sample code on GitHub here.
What Are We Building Today?
My background is primarily building fitness apps. In these types of apps it’s always critical to have beautiful charts as eye candy. This makes me gravitate towards charts when I think of animations. I’ve created a simple chart for us to build and animate:
Project Setup
To get started, install ReAnimated 2 and React Native SVG. The full instructions for setting up ReAnimated 2 are available on the
website here. I’ll put my version of the instructions into this article for convenience.TypeScript Instructions
Start by generating the project using this command in the terminal.
npx react-native init SVGAnimationSample --template react-native-template-typescript
Then, add react-native-svg
and react-native-reanimated
by running yarn add
at the root of your project.
yarn add react-native-svg react-native-reanimated
Next, add the following to your babel.config.js
to enable the ReAnimated plugin for the Metro Bundler.
module.exports = {
...
plugins: ['react-native-reanimated/plugin'],
};
If you are having trouble with this step check out my metro config here.
Android Instructions
You’ll need to activate Hermes at this point. Hermes is the new JavaScript Engine for React-Native produced by Facebook. Unfortunately, this will disable your ability to use the Chrome debugger. Luckily, Facebook provided us with Flipper for debugging with Hermes apps if you need that functionality. We won’t be setting it up in this tutorial though.
To enable Hermes, start in the android
directory’s build.gradle
file, find this line, and change enableHermes
to true
.
project.ext.react = [
enableHermes: true
]
Then find this line and do the same.
def enableHermes = project.ext.react.get("enableHermes", true);
Finally, add these lines to your MainApplication.java
.
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage; ...
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
Doing this is always pretty tedious, if you get lost, check out my gradle file.
iOS Instructions
Make sure your :hermes_enabled
variable is set to true
in your ios/Podfile
then run pod install
in the ios
directory of your app in the terminal.
Making a Custom Circle SVG
Alright, now that the boring stuff is out of the way, let’s write some code 🧑💻! First, clear out the boilerplate code in the App.tsx
file generated with our project. You can replace the App.tsx
boilerplate with my code in the Gist below. All I do here is declare a radius for our circle and pass in basic props like the colour of the chart and the percentage of it we want to fill.
Next, create a file named CircularProgress.tsx
under the root directory. We’ll import react-native-svg
and create our prop type.
import React, {FC} from 'react
import {View, Text} from 'react-native'
import Svg, {Circle} from 'react-native-svg'type CircularProgressProps = {
strokeWidth: number;
radius: number;
backgroundColor: string;
percentageComplete: number;
}
Now create the container style for our component so it can be used in the CircularProgress
component’s container.
const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
justifyContent: 'center',
alignItems: 'center',
}
})
At long last, let’s add our SVG to the component.
Your CircularProgress.tsx
file so far should look like this:
This code will give us a really vanilla, uninteresting, circle 😑.
Making Our Circle into a Chart
Let’s 🌶 this up a little and hollow out the circle so it looks a more like a chart. We start by calculating the inner radius of the circle. This value is needed because the strokeDashoffset
takes up some space outside the circle. To fit our component in the space allotted for it, using the inner radius forces the results of our calculations to remain within the container.
const innerRadius = radius - strokeWidth / 2;
We calculate circumference based on the inner radius for the same reason. Our circumference should fit in the component’s container.
const circumfrence = 2 * Math.PI * innerRadius;
Now change the fill colour to transparent, change the circumfrence to the value we just calculated, and adjust the r value to be the innerRadius
in our SVG.
Sweet 🙂 this is finally starting to look like a chart 👏.
An Aside About How Circles Work
Let’s tinker around with strokeDashoffset
so we can understand how it works before animating anything. As we already know, a full revolution of a circle is 2𝛑r. Let’s make the following change to our circle so we can have a strokeDashOffset
modified from its default value we’ve been using so far.
If we multiply innerRadius
in the dashstrokeOffset
prop by 50% we see that dashstrokeOffset
actually moves and becomes a half circle. This is because we are reducing the offset based on a smaller value of 𝛑. We can use this property of circles to animate our SVG 🤯.
Let’s Start Animating
Pretty cool right? But you’re not here to learn about circles, thats boring High School math, let’s animate this chart 😤!
The first thing to do is wrap our circle using createAnimatedComponent
. We do this because SVG Circle by itself is not animatable.
const AnimatedCircle = Animated.createAnimatedComponent(Circle);
Then just replace Circle with AnimatedCircle in the markup.
<View style={styles.container}>
<Svg style={StyleSheet.absoluteFill}>
<AnimatedCircle
...
Next, wrap the values we use for the circle animation in values from the ReAnimated 2 library. We are going to use three types of these special animated values here. useSharedValue
creates a shared memory variable between the UI thread and TypeScript. useDerivedValue
allows us to perform computations on variables that exist in the UI thread. Finally, useAnimatedProps
allows you to manipulate styles on the UI thread from JavaScript. No bridging is used here like in the legacy animation libraries. All manipulations are done through shared memory.
Before wrapping our values, we have to start by inverting the percentage complete that we want to show so it works with the dashstrokeOffset
. It looks like this:
const invertedCompletion = (100 - percentageComplete) / 100;
Now let’s wrap our theta
value in a shared value so we can use it for animations.
const theta = useSharedValue(2 * Math.PI);
We can then compute the value we want to animate to using useDerivedValue
.
const animateTo = useDerivedValue(() => 2 * Math.PI * invertedCompletion);
Now let’s use animated props instead of the default strokeDashoffset
so the animation grows as time passes. We are going to use a transition here. Transitions are mini animations provided by ReAnimated to create animated effects. The transition we are going to use here is withTiming
. You basically supply it with a value, and it changes for the duration of time you provide. The animated prop ends up looking like this:
const animatedProps = useAnimatedProps(() => {
return {
strokeDashoffset: withTiming(theta.value * innerRadius, {
duration: 1500,
}),
};
});
We then need to activate the animation by setting a new value to theta
. To do this we’ll add a button to the app that updates the theta
when pressed. Your animation code should now look like this:
The animation should now look like this:
Finishing Touches
Let’s complete this chart by showing the percentage and having everything fade in using an opacity. We start by creating a global variable with how fast we want everything to fade in.
const FADE_DELAY = 1500;
Then we create a shared value for the opacity of text.
const textOpacity = useSharedValue(0);
Let’s now create a couple of styles for fading in the text.
const powerTextStyle = useAnimatedStyle(() => {
return {
opacity: withTiming(textOpacity.value, {
duration: FADE_DELAY,
}),
};
});const powerPercentTextStyle = useAnimatedStyle(() => {
return {
opacity: withTiming(textOpacity.value, {
duration: FADE_DELAY,
}),
};
});
Next, add a couple Animated.Text
components that we can place overtop the chart.
<Animated.Text style={[styles.powerText, powerTextStyle]}>
Power %
</Animated.Text><Animated.Text style={[styles.powerPercentage, powerPercentTextStyle]}>
{percentageComplete}
</Animated.Text>
At this point, we can finish up by adding a button that animates in and out whenever we press it by changing the theta
value and the textOpacity
value.
<Button
title="Animate!"
onPress={() => {
if (!textOpacity.value) {
theta.value = animateTo.value;
textOpacity.value = 1;
} else {
theta.value = 2 * Math.PI * 1.001;
textOpacity.value = 0;
}
}}
/>
Your full CircularProgress.tsx
should now look like this:
And there you have it! Your first SVG animation with ReAnimated 2 🤜 🤛.
I hope you enjoyed building this 📊 animation with ReAnimated 2. I’ve personally had a ton of fun with this library since it’s come out and I hope you’ll enjoy it as much as I have 😀.
Daniel is an Agile Software Developer specializing in Flutter and React-Native. As much as he likes cross platform he is passionate about all types of mobile development including native iOS and Android and is always looking to advance his skill in the mobile development world.
TribalScale is a global innovation firm that helps enterprises adapt and thrive in the digital era. We transform teams and processes, build best-in-class digital products, and create disruptive startups. Learn more about us on our website. Connect with us on Twitter, LinkedIn & Facebook!