Originally published π https://elazizi.com/
If you are using Spotify mobile application to listen to your favorites music, for sure you noticed the great and smooth login animation.
As a Front-end and React Native developer I easily fell in love with such kind of micro-interaction and I always aim to create my own version.
Before jumping into our use case, I just want to highlight that having such kind of micro-interaction in your mobile application is very important for users, Using those animations would help to improve the user experience and will make your application special in front of all standard animation we have in Mobile Platform.
Last weekend, during the quarantine time π. I start thinking about Implementing the same behavior in one of the applications I am working on using react-navigation API.
This Article is my weekend work trying to implement Spotify login animation using react-navigation V5.
In case you are not familiar with Spotify and didnβt know how the Spotify login animation looks like. The login animation is a flip from the login screen to the home page. The flip Animation can be implemented easily using the react-native animation API, but the challenge here is to implement the animation using Navigation API.
Approach :
To Implement the Animation we should first prepare our login and home screens to make sure our example will reflect the exact Spotify app experience, then we should implement an AuthContext
to handle our authentication flow.
Next, we are going to create our Navigators and set up the navigation experience with our authentication workflow.
Our last step is to create the custom flip animation using the new react-navigation API.
If you are Familiar with React-Navigation and you already woked with authentication flow before, you can skip all those sections to Create Spotify Transition.
Create Screens
To make it easy to follow, we are not going to create a Spotify clone, instead, we will only focus on creating the login and home screen with Albums.
Create the Login Screen using a Simple Form and a Login Button.
// screens/Login.js
import * as React from "react";
import { Text, View, StyleSheet } from "react-native";
import { Button, TextInput } from "react-native-paper";
export const LogIn = ({ navigation }) => {
const [loading, setLoading] = React.useState(false);
const loginApi = () => {};
return (
<View style={styles.form}>
<TextInput label="Username" style={styles.textInput} />
<TextInput
label="Password"
style={styles.textInput}
secureTextEntry={true}
/>
<Button mode="outlined" onPress={() => loginApi()} style={styles.button}>
{loading ? "Loading ...." : "LogIn"}
</Button>
</View>
);
};
Make sure to read My previous post about the best way to handle forms in react native
Next step is to create the home screen with some albums cover images :
import * as React from "react";
import { StyleSheet, Image, ScrollView } from "react-native";
import { Button, TextInput } from "react-native-paper";
export const Home = ({ navigation }) => {
return (
<ScrollView contentContainerStyle={styles.albums}>
<Albums />
</ScrollView>
);
};
Create Navigators and Auth Flow
To set-up our authentication flow we are going to use the same approach mentioned in react-navigation documentation with some changes on the navigation Part.
First, we are going to make an AuthContext
& AuthProvider
that will help us deal with all authentication flow.
For the navigation part, I think the best way for our case is to create two stacks, one for auth screens (login, signUp, β¦) and a second one for home screens ( albums, player β¦)
Finally, we use a root stack navigator that renders the correct stack based on user status
( loggedIn/Out).
// navigation/index.js
const AuthStack = createStackNavigator();
const AppStack = createStackNavigator();
const RootStack = createStackNavigator();
const AuthNavigator = () => {
return (
<AuthStack.Navigator
headerMode="float"
screenOptions={{
cardOverlayEnabled: true,
gestureEnabled: true,
}}
>
<AuthStack.Screen name="Login" component={LogIn} />
</AuthStack.Navigator>
);
};
const HomeNavigator = () => {
return (
<AppStack.Navigator
headerMode="float"
screenOptions={{
cardOverlayEnabled: true,
gestureEnabled: true,
}}
>
<AppStack.Screen
name="Albums"
component={Home}
options={{
headerRight: () => <SignOutButton />,
}}
/>
</AppStack.Navigator>
);
};
export const RootNavigator = () => {
const { status } = useAuth();
return (
<RootStack.Navigator headerMode="none">
{status === "signOut" ? (
<RootStack.Screen name="Auth" component={AuthNavigator} />
) : (
<RootStack.Screen name="App" component={HomeNavigator} />
)}
</RootStack.Navigator>
);
};
By the end, we should be able to login using the login screen.
Create Spotify Transition
React stack navigation v5 release introduced a new way to create custom Animation using declarative API and based on next and current screen progress.
Those transactions can be customized by using screen options prop for every screen, but in our case, we are going to implement the custom animation globally for our Root Navigator.
For stack Navigator There are 4 animation related options:
-
gestureDirection
: to define your swipe gestures. -
transitionSpec
: An object which specifies the animation type (timing or spring) and their options (such as the duration for timing). -
cardStyleInterpolator
: This is a function that specifies interpolated styles for various parts of the card. Using this function we can interpolate styles for the container, the card itself, overlay and shadow, Our custom animation will be based on using this function. -
headerStyleInterpolator
: The same as cardStyleInterpolate but for header properties. To create a custom transition for our Root screens we need to create a custom transition object for our Transition and add it toscreenOptions
prop inRootNavigator
// navigation/SpotifyTransition.js
import { Easing } from "react-native";
import {
TransitionSpecs,
HeaderStyleInterpolators,
} from "@react-navigation/stack";
const AnimationSpec = {
animation: "timing",
config: {
duration: 2000,
easing: Easing.ease,
},
};
export const SpotifyTransition = {
transitionSpec: {
open: AnimationSpec,
close: AnimationSpec,
},
cardStyleInterpolator: ({ current, next }) => {
return {};
},
};
// navigation/index.js
export const RootNavigator = () => {
const { status } = useAuth();
return (
<RootStack.Navigator
headerMode="none"
screenOptions={{
cardOverlayEnabled: true,
gestureEnabled: true,
...SpotifyTransition,
}}
>
{status === "signOut" ? (
<RootStack.Screen name="Auth" component={AuthNavigator} />
) : (
<RootStack.Screen name="App" component={HomeNavigator} />
)}
</RootStack.Navigator>
);
};
The first step in our transition is to rotate the focused screen(the screen we want to navigate to) according to Y-axis from 180deg
to 0deg
and at the same time, the next screen should rotate from 0deg
to 180deg
.
To Make it work, we need to update card Style and set rotateX as the following:
export const SpotifyTransition = {
transitionSpec: {
open: AnimationSpec, //TransitionSpecs.TransitionIOSSpec,
close: AnimationSpec, //TransitionSpecs.TransitionIOSSpec,
},
cardStyleInterpolator: ({ current, next }: any) => {
return {
cardStyle: {
transform: [
{
rotateY: next
? next.progress.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "180deg"],
})
: current.progress.interpolate({
inputRange: [0, 1],
outputRange: ["180deg", "0deg"],
}),
},
],
},
};
},
};
The highlighted lines mean that if we have the next value, we know that this card is behind the focused one, so we need to rotate from 0deg
to 180deg
.
As you can see the rotation is working as expected but the focused screen is always on the top and covers the old one immediately even if the Transition progress is still running.
To fix this issue, we are going to interpolate the navigation progress and use it for card opacity which will help us show the focused screen only when the progress is higher than 0.5 and show the old screen if it's not the case
export const SpotifyTransition = {
transitionSpec: {
open: AnimationSpec,
close: AnimationSpec,
},
cardStyleInterpolator: ({ current, next }) => {
return {
cardStyle: {
opacity: next
? next.progress.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [1, 1, 0],
})
: current.progress.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 0, 1],
}),
transform: [
{
rotateY: next
? next.progress.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "180deg"],
})
: current.progress.interpolate({
inputRange: [0, 1],
outputRange: ["180deg", "0deg"],
}),
},
],
},
overlayStyle: {
opacity: current.progress.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
}),
},
};
},
};
Now it's time to make the transition smooth using a spring IOS Transition from React Navigation instead of a simple timing config.
export const SpotifyTransition = {
transitionSpec: {
open: TransitionSpecs.TransitionIOSSpec,
close: TransitionSpecs.TransitionIOSSpec,
},
...
},
};
Github repo with snack demo π https://github.com/yjose/spotify-login-animation-with-react-navigation-v5
I hope you found that interesting, informative, and entertaining. I would be more than happy to hear your remarks and thoughts.
If you think other people should read this post. Tweet, share and Follow me on twitter for the next articles.
Reac More π Forms in React Native, The right way
Top comments (0)