Introduction
This guide works for react-navigation-stack (Stack Navigator for React Navigation 4) as well as @react-navigation/stack 5.0 (part of React Navigation 5).
Have you ever wanted to implement a custom transition animation for React Navigation’s stack navigator? Then you’ve come to the right place. In this guide, we’ll learn how to implement a custom screen transition animation from scratch.
The guide assumes basic knowledge about animations in React Native, specifically interpolations. If you're not familiar with interpolations, take a look at the official docs for Animated.
Stack Navigator
Recently the Stack Navigator was re-written and has a completely new API for defining transitions. It allows us to customize the transition for various parts of our screen as well as the header. The animations we define integrate seamlessly with the swipe gesture as well. We'll implement a custom animation in the guide to get familiar with the concepts.
We are aiming for the following animation:
Before writing some code, let's split the animation into small parts. During the animation/gesture, we can notice the following:
- The focused screen moves and rotates
- There's a overlay behind the focused screen which gets darker/lighter
- The screen behind scales up/down
- The header items cross-fade
Let's take a look at the official docs for Stack Navigator animations. There are 4 animation related options:
- <rte-code>gestureDirection<rte-code>
- <rte-code>transitionSpec<rte-code>
- <rte-code>cardStyleInterpolator<rte-code>
- <rte-code>headerStyleInterpolator<rte-code>
We want the screen to respond to horizontal gestures, so we'll set <rte-code>gestureDirection<rte-code> to <rte-code>horizontal<rte-code>. For <rte-code>transitionSpec<rte-code>, we need to specify the configuration to use for the transition (e.g. <rte-code>spring<rte-code> animation and some options for it). To keep things simple, let's use one of the built-in configs for this. For the header, we'll use the built-in fade animation. So for now, our custom transition looks like this:
To use this configuration in our navigator, we can pass it in options (React Navigation v5):
Or <rte-code>navigationOptions<rte-code> (Stack v2 with React Navigation v4):
To use the animation for all screens, we can pass it in <rte-code>screenOptions<rte-code> (React Navigation v5) or <rte-code>defaultNavigationOptions<rte-code> (Stack v2 with React Navigation v4).
Now let's get to the interesting parts, <rte-code>cardStyleInterpolator<rte-code> and <rte-code>headerStyleInterpolator<rte-code>. So what are these?
Interpolating transitions in React
The API for transitions is based on the concept of interpolations. If you have some math background, the interpolation idea should ring a bell and it’s exactly what you should remember from your math classes. React Navigation gives us the animated nodes for the current screen in the stack (<rte-code>current.progress<rte-code>) and screen after that one (<rte-code>next.progress<rte-code>). The <rte-code>progress<rte-code> values are the animation/gesture progress. When the screen in first starts opening, the value will be <rte-code>0<rte-code> and when it's fully open, the value will be <rte-code>1<rte-code>. We can interpolate on those and specify styles for various parts of the screen.
So let's write the styles for the card (the part of the screen which contains the content) first. We want our focused card to rotate and translate based on the <rte-code>progress<rte-code> value of the card.
Here, the card starts from a translate value of screen width and 1 radian rotation to no translate and zero rotation.
We also want the previous card to scale down. If we have a next value, we know that the card is behind another card, so we need to scale it down based on the next card's progress:
Next part is the overlay behind the card. It's black by default, so we just need to change its opacity:
Now let's put it all together in our custom transition under <rte-code>cardStyleInterpolator<rte-code>. The styles for the card will be in <rte-code>cardStyle<rte-code> and for overlay, it'll be <rte-code>overlayStyle<rte-code>:
And we're done! You can check the complete demo at snack and run it on your device.
We have only implemented the card animation here, but the same principles apply for the header as well. For more details on what options are available, check the official docs.
Do you have any questions? Drop a comment below or tweet to me @satya164. Can't wait to see what animations you come up with!
As part of The React Native Show, we released a podcast fully dedicated to future of React Navigation.