How to create React Native SVG morphing animations

Create shape shifting animations in your apps in no time

Mikael Ainalem
5 min readJul 29, 2024
Photo by Chris Lawton on Unsplash

Morphing is a great, playful way to entice your users. It’s ideal for enhancing micro-interactions — those delightful moments designed to draw your audience’s attention to the UI.

There are many situations where morphing effects are suitable. This article explores one of the most classic uses: morphing a play symbol into a pause symbol and vice versa. First, let’s take a look at what we’re building. Here’s the animation:

Play/pause morphing effect in iOS simulator

Modeling in a visual editor

To build a model, we need to consider the shapes we want to morph between. In this case, the animation involves two shapes. One of them, the pause symbol, consists of two bars, while the play symbol consists of a single shape: a triangle with rounded corners. To make the model work, we’ll cut the play symbol in half, creating two pairs of shapes that we can animate between. Here’s what the model looks like in the vector editor I work with, Inkscape:

Model of the play/pause icons

The idea is for the purple parts to transform into the cyan parts and vice versa, in a quick animation lasting around 300–500ms. As you might have noticed, the pause symbol is rotated 90 degrees in the above model. This is intentional, as I want the morphing to be a subtle effect. The shape shifting should occur without the shapes moving too much, as morphing and moving simultaneously can look janky. To fix the orientation, we’ll rotate the whole model 90 degrees when animating between the two end states.

Another detail to note is the small overlap between the two halves of the play symbol. This is deliberate, ensuring that the animation tightly closes the gap between the two parts when transitioning from the two bars to the play triangle.

One important thing to keep in mind when working with morphing is that the shapes need to be consistent with each other. You’ll need to make sure that vector objects consist of the same number of nodes. You might need to turn off path optimization in your editor. In this animation, both the bar and the half triangle consists of 64 vector nodes without curvature.

Drawing the model

This article focuses solely on the coding aspects of creating the effect. To create the model, you’ll need to be handy with a vector editor and know how to draw vector shapes. There are plenty of tutorials available, or you can collaborate with a designer. I’m deliberately skipping this part.

The model in code

Next, let’s move from the visual editor to the code. Open the SVG model file in your IDE to see what the vector looks like in code. Notice how each shape is represented by a <path /> node, with its curvature described by the d="..." attribute. Below is an extract, containing the most interesting parts, of what the code looks like:

<svg
width="100"
height="100"
viewBox="0 0 100 100">
<path
id="playTriangleBottom"
d="M 37.507428,62.224555 37.511661,61.433069 ..." />
<path
id="pauseBarBottom"
d="M 64.551731,65.496722 61.261197,65.496429 ..." />
<path
id="playTriangleTop"
d="M 37.507428,37.776311 37.511661,38.567791 ..." />
<path
id="pauseBarTop"
d="M 64.551731,34.503277 61.261197,34.503565 ..." />
</svg>

Another good idea is naming the different graphical parts in your editor. This makes it possible to distinguish the paths in code by their id attribute.

Moving SVG to React Native

Once we have the model in place, the next step is to move from the SVG realm to React Native. Now, we want the model to come to life in the apps we’re building. First, let’s create an empty app and add the react-native-svg package:

# Create a new project
npx react-native init ReactNativePlayPauseButton

# Jump into it
cd ReactNativePlayPauseButton

# Add the RN SVG package
yarn add react-native-svg

Next, fire up your development environment, expo or whatever you prefer and clear out the App.tsx.

# Run the code on iOS
yarn && npx pod-install && yarn ios

# Or run it on Android
yarn && yarn android

Making the react-native components work with React Native Animated

The react-native-svg Path component only works with the React Native Animated component, which is essentially the animation system, if they are wrapped first. Wrapping these components looks like this:

import {Svg, Path, ... from 'react-native-svg';

const AnimatedSvg = Animated.createAnimatedComponent(Svg);
const AnimatedPath = Animated.createAnimatedComponent(Path);

Note that I’m also wrapping the Svg component here, as mentioned above, because I plan to animate its orientation. I intend to rotate the wrapped AnimatedSvg node 90 degrees using the same React Native Animated.Value object.

Making things move

Let’s create the values object to build the effect around. I’m using a range from 0 to 1 for the animation, as I find it easy to work with, but you can choose any range you prefer.

const anim = new Animated.Value(0);

Next is the interpolation. This is the actual shape-shifting part, where the interpolate function calculates each frame of the animation. Here, we'll also copy and paste the paths from the SVG file.

const partInterpolation1 = anim.interpolate({
inputRange: [0, 1],
outputRange: [
'M 37.507428,62.224555 37.511661,61.433069 ... Z',
'M 64.551731,65.496722 61.261197,65.496429 ... Z',
],
});
const partInterpolation2 = anim.interpolate({
inputRange: [0, 1],
outputRange: [
'M 37.507428,37.776311 37.511661,38.567791 ... Z',
'M 64.551731,34.503277 61.261197,34.503565 ... Z',
],
});

And then we need a trigger function to fire off the animation when the user presses the play/pause button.


const App = () => {
const [state] = useState(getInitialState());
const [play, setPlay] = useState(true);

const toggleAnimation = () => {
const {anim} = state;
Animated.timing(anim, {
toValue: play ? 1 : 0,
duration: 300,
easing,
useNativeDriver: false,
}).start();
setPlay(!play);
};

// ...
};

All together now

Last but not least, the JSX markup to visualize the whole thing containing all the above to tie everything together. Here we got

  • A Pressable node to trigger the animation
  • The wrapped nodes AnimatedSVG and AnimatedPath
  • The interpolations to drive the animation
const App = () => {
// ...

const {rotateInterpolation, partInterpolation1, partInterpolation2} = state;
const rotateStyle = {
transform: [{rotate: rotateInterpolation}],
};

return (
<Pressable onPress={toggleAnimation}>
<View style={styles.container}>
<Text style={styles.title}>
React Native{' '}
<Text style={[styles.title, styles.titleLight]}>
- Morphing play/pause button
</Text>
</Text>
<AnimatedSvg
width={100}
height={100}
viewBox="0 0 100 100"
style={rotateStyle}>
<Circle cx="50" cy="50" r="50" fill="red" />
<AnimatedPath d={partInterpolation1} fill="#fff" />
<AnimatedPath d={partInterpolation2} fill="#fff" />
</AnimatedSvg>
</View>
</Pressable>
);
};

That’s it! If you made it this far, thanks for reading and good luck with your morphing animations!

You can find the repo here would you like to dig deeper. Cheers!

--

--

Mikael Ainalem
Mikael Ainalem

Written by Mikael Ainalem

Enthusiastic about software & design, father of 3, freelancer and currently CTO at Norban | twitter: https://twitter.com/mikaelainalem

No responses yet