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.

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:

gestureDirection

transitionSpec

cardStyleInterpolator

headerStyleInterpolator

We want the screen to respond to horizontal gestures, so we’ll set gestureDirection to horizontal . For transitionSpec , we need to specify the configuration to use for the transition (e.g. spring 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:

1 2 3 4 5 6 7 8 9 10 import { TransitionSpecs , HeaderStyleInterpolators } from '@react-navigation/stack' ; const MyTransition = { gestureDirection : 'horizontal' , transitionSpec : { open : TransitionSpecs . TransitionIOSSpec , close : TransitionSpecs . TransitionIOSSpec , } , headerStyleInterpolator : HeaderStyleInterpolators . forFade , }

To use this configuration in our navigator, we can pass it in options (React Navigation v5):

1 2 3 4 5 6 7 8 < Stack . Screen name = "Test" component = { TestScreen } options = { { title : 'Custom animation' , . . . MyTransition , } } / >

Or navigationOptions (Stack v2 with React Navigation v4):

1 2 3 4 5 6 7 8 function TestScreen ( ) { // content } TestScreen . navigationOptions = { title : 'Custom animation' , . . . MyTransition , } ;

To use the animation for all screens, we can pass it in screenOptions (React Navigation v5) or defaultNavigationOptions (Stack v2 with React Navigation v4).

Now, let’s get to the interesting parts, cardStyleInterpolator and headerStyleInterpolator . So what are these?

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 ( current.progress ) and screen after that one ( next.progress ). The progress values are the animation/gesture progress. When the screen in first starts opening, the value will be 0 and when it’s fully open, the value will be 1 . 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 progress value of the card.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 { transform : [ { translateX : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ layouts . screen . width , 0 ] , } ) , } , { rotate : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 1 , 0 ] , } ) , } , ] , }

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:

1 2 3 4 5 6 7 8 9 10 11 12 { transform : [ { scale : next ? next . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 1 , 0.9 ] , } ) : 1 , } , ] , }

Next part is the overlay behind the card. It’s black by default, so we just need to change its opacity:

1 2 3 4 5 6 { opacity : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 0 , 0.5 ] , } ) , }

Now let’s put it all together in our custom transition under cardStyleInterpolator . The styles for the card will be in cardStyle and for overlay, it’ll be overlayStyle :

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 const MyTransition = { gestureDirection : 'horizontal' , transitionSpec : { open : TransitionSpecs . TransitionIOSSpec , close : TransitionSpecs . TransitionIOSSpec , } , headerStyleInterpolator : HeaderStyleInterpolators . forFade , cardStyleInterpolator : ( { current , next , layouts } ) = > { return { cardStyle : { transform : [ { translateX : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ layouts . screen . width , 0 ] , } ) , } , { rotate : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 1 , 0 ] , } ) , } , { scale : next ? next . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 1 , 0.9 ] , } ) : 1 , } , ] , } , overlayStyle : { opacity : current . progress . interpolate ( { inputRange : [ 0 , 1 ] , outputRange : [ 0 , 0.5 ] , } ) , } , } ; } , }

And we’re done! You can check the complete demo at the snack below and run it on your device:

https://snack.expo.io/@satya164/custom-animation-in-react-navigation-5

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!