The 🍔 Menu— animating the menu icon
Creating an open/close animation in 📱React Native
People seem to never seem to grow tired of the hamburger menu. It is perhaps the most iconic of UI widgets when it comes to mobile apps and mobile web pages. I’ve previously written a comprehensive article of how to implement the widget on the web using the SVG line animation technique: here. This article focuses on how to get a similar effect up and running in React Native.
As usual let’s start with what we’re building. Here’s what the effect looks like.
Building the model
First, as with most animations I create, I start by considering the underlying model. This version of the menu differs slightly from the typical hamburger menu. Usually, a hamburger menu consists of three horizontal lines — representing the bun and the patty, hence the name. In this case, however, I’m using an ellipsis (three consecutive dots) to indicate that additional actions are hidden in a menu that the user can access by tapping the icon.
If we break down the menu animation we can see that it consists of 3 keyframes. They are:
- The three dots horizontally aligned
- The three dots diagonally aligned
- And finally the closing X
When creating the animation, each of the three periods should be represented by an individual RNSVG Path
element. This allows us to interpolate the path descriptors between the keyframes to generate the animation. Each Path
element will consist of two vector nodes. With two nodes per path, we can position them closely to form a period or spread them apart to create a stretched line. Also, we’ll set the stroke-linecap
to round
, ensuring the period appears as a filled circle. Here's the model in Inkscape, my preferred vector editor, where I’ve placed the keyframes in separate layers for clarity and structure.
Going between the keyframes
Transitioning from the first keyframe to the second is straightforward; it simply involves moving the first and third paths along the vertical y-axis. However, the shift to the third keyframe requires transforming the periods into lines. This is where the two nodes come into play — by moving one of the nodes, we can stretch the dot into a line. Below is an GIF animation that explains how to create this transformation in the vector editor.
Going from model to code
The next step is translating the model into code. To animate in React Native, we first need to wrap the Path
element from react-native-svg
to make it animatable.
const AnimatedPath = Animated.createAnimatedComponent(Path);
Secondly, we need a Animated.Value to run the animation.
const [anim] = useState(new Animated.Value(0));
And then a toggle function to trigger the animation.
const easing = Easing.bezier(0.4, 0, 0.2, 1);
const toggleAnimation = () => {
Animated.timing(anim, {
toValue: open ? 0 : 1,
duration: 400,
easing,
useNativeDriver: true,
}).start();
setOpen(!open);
};
Next, we define the interpolations for the visual elements that we want to animate. We use the path descriptors from the model SVG file as the values for the interpolation of the menu parts. This part is mostly an exercise in copy and paste. Note: It’s a good idea to learn how path descriptors works. Then you more easily debug problems if your conversion from model to code fails.
const partInterpolation1 = anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: ['M 3,12.5 3.01,12.5', 'M 3,3 3.001,3', 'M 3,3 12.5,12.5'],
});
const partInterpolation2 = anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [
'M 12.5,12.5 12.5005,12.5 12.501,12.5',
'M 12.5,12.5 12.5005,12.5 12.501,12.5',
'M 3,22 12.5,12.5 22,3',
],
});
const partInterpolation3 = anim.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [
'M 22,12.5 22.01,12.5',
'M 22,22 22.01,22',
'M 12.5,12.5 22,22',
],
});
const sheetInterpolation = anim.interpolate({
inputRange: [0, 1],
outputRange: [Dimensions.get('window').width * -1, 0],
});
const sheetAnimationStyle = {
transform: [
{
translateX: sheetInterpolation,
},
],
};
And then the JSX markup. Note, the Pressable
node which calls the trigger function to play the animation and change the state.
<SafeAreaView style={backgroundStyle}>
<View style={styles.container}>
<View style={styles.topBar}>
{/* Back icon */}
<Svg width="25" height="25" viewBox="0 0 25 25" fill="none">
<Path
d="M 17,3 L 8,12.5 L 17,22"
fill="none"
stroke="#282828"
strokeWidth={3}
strokeLinecap="round"
/>
</Svg>
{/* Title */}
<View>
<Text style={styles.title}>Urban Suburban</Text>
</View>
{/* Hamburger menu */}
<Pressable onPress={toggleAnimation}>
<View style={styles.hamburger}>
<Svg width="25" height="25" viewBox="0 0 25 25" fill="none">
<AnimatedPath
d={partInterpolation1}
fill="none"
stroke="#282828"
strokeWidth={3}
strokeLinecap="round"
/>
<AnimatedPath
d={partInterpolation2}
fill="none"
stroke="#282828"
strokeWidth={3}
strokeLinecap="round"
/>
<AnimatedPath
d={partInterpolation3}
fill="none"
stroke="#282828"
strokeWidth={3}
strokeLinecap="round"
/>
</Svg>
</View>
</Pressable>
</View>
{/* Other markup */}
</SafeAreaView>
Getting the touch area just right
For usability, it’s important to ensure the touch area is the right size. In the inspector, you can see the touch area with extra padding, making it easier for users to interact with the menu without missing it.
Accessibility
When going to production we want to make sure that the menu also is properly accessible. Let’s ask ChatGPT to review the code
— I have this code, can you review it to ensure it is accessible? It is used for a hamburger menu in an app where users can punch the menu to show an overlay sheet to navigate to other sections of the app. <Pressable ...>...</Pressable>
ChatGPT suggested the following change:
<Pressable
onPress={toggleAnimation}
accessibilityRole="button"
accessibilityLabel="Open menu"
accessibilityHint="Opens a navigation menu"
>
{/* ... */}
</Pressable>
TLDR;
- Use a vector editor to create your model
- Copy the descriptors to your React Native component
- Use the React Native Animated and interpolate to create the animation
- Use ChatGPT to make sure your code is accessible
Thanks for reading and the best of luck with your animations. Cheers!
You can find the repository: here on Github