How to create React Native SVG morphing animations
Create shape shifting animations in your apps in no time
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:
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:
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
andAnimatedPath
- 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!