Create an Animated Onboarding Flow with React Native Reanimated

Create an Animated Onboarding Flow with React Native Reanimated

In this tutorial, we will walk through how to build an animated onboarding flow using React Native Reanimated. We'll break down each component used for pagination, buttons, and dot indicators, and explore the animations involved. By the end of this guide, you'll have a smooth, interactive onboarding experience ready to be implemented in your React Native app.

Watch the tutorial
Watch on YouTube

Introduction

An onboarding flow is a crucial part of any mobile app. It introduces users to the core features and helps them get started quickly. To make this process more engaging, adding subtle animations can enhance the experience. In this tutorial, we will build a multi-step onboarding flow with animated pagination and navigation buttons using Reanimated.

Grab the code

loading...

Prerequisites

Before getting started, ensure you have React Native Reanimated set up in your project. If you're new to it, follow their installation guide for instructions.

Now, let’s dive into creating an animated onboarding experience.


Step 1: Setting Up Constants

First, we’ll define some constants that control spacing, dot size, and animation properties. These will ensure a consistent layout across all components.

1const _spacing = 8;
2const _buttonHeight = 42;
3const _layoutTransition = LinearTransition.springify()
4 .damping(80)
5 .stiffness(200);
6const _dotContainer = 24;
7const _dotSize = _dotContainer / 3;
8const _activeDot = "#fff";
9const _inactiveDot = "#aaa";
10
11
  • _spacing: General spacing between elements.
  • _buttonHeight: Height of the onboarding navigation buttons.
  • _layoutTransition: A spring-based layout transition that provides smooth movement.
  • _dotContainer: Size of the dot container.
  • _dotSize: Actual size of the dot.
  • _activeDot and _inactiveDot: Colors for active and inactive dots.

These constants will be used throughout the tutorial to standardize the layout and animations.


Step 2: Creating the Button Component

The Button component will be used for navigation between the onboarding steps. We'll use Reanimated's AnimatedPressable to create a button with animated enter and exit effects.

Button Component

1function Button({ children, style, ...rest }: AnimatedProps<PressableProps>) {
2 return (
3 <AnimatedPressable
4 style={[
5 {
6 height: _buttonHeight,
7 borderRadius: _buttonHeight / 2,
8 justifyContent: "center",
9 alignItems: "center",
10 paddingHorizontal: _spacing * 2,
11 },
12 style,
13 ]}
14 entering={FadeInLeft.springify().damping(80).stiffness(200)}
15 exiting={FadeOutLeft.springify().damping(80).stiffness(200)}
16 layout={_layoutTransition}
17 {...rest}
18 >
19 {children}
20 </AnimatedPressable>
21 );
22}
23
24

Explanation:

  • Layout and Style: We give the button a round shape by setting the borderRadius to half of its height. It also has padding for spacing.
  • Animations: When the button appears, it enters with a FadeInLeft animation, and when it disappears, it exits with a FadeOutLeft animation. Both use spring-like physics to ensure smooth transitions.

Step 3: Creating the Pagination Dots

Next, we’ll create animated dots for the pagination. The dots will change color based on the current onboarding step.

Dot Component

1function Dot({ index, animation }: { index: number; animation: SharedValue<number>; }) {
2 const stylez = useAnimatedStyle(() => {
3 return {
4 backgroundColor: interpolateColor(
5 animation.value,
6 [index - 1, index, index + 1],
7 [_inactiveDot, _activeDot, _activeDot]
8 ),
9 };
10 });
11
12 return (
13 <View style={{ width: _dotContainer, height: _dotContainer, justifyContent: "center", alignItems: "center" }}>
14 <Animated.View style={[stylez, { width: _dotSize, height: _dotSize, borderRadius: _dotSize }]} />
15 </View>
16 );
17}
18
19

Explanation:

  • interpolateColor: This function interpolates the background color based on the current index, changing the dot color to _activeDot (white) when selected and _inactiveDot (gray) when inactive.
  • useAnimatedStyle: We use Reanimated's useAnimatedStyle to dynamically change the dot's color during the transition between steps.

Step 4: Adding a Pagination Indicator

The PaginationIndicator is a moving element that highlights the active dot. It grows and shrinks as the user navigates through the onboarding steps.

PaginationIndicator Component

1function PaginationIndicator({ animation }: { animation: SharedValue<number>; }) {
2 const stylez = useAnimatedStyle(() => {
3 return {
4 width: _dotContainer + _dotContainer * animation.value,
5 };
6 });
7
8 return (
9 <Animated.View
10 style={[
11 {
12 backgroundColor: "#29BE56",
13 height: _dotContainer,
14 width: _dotContainer,
15 borderRadius: _dotContainer,
16 position: "absolute",
17 left: 0,
18 top: 0,
19 },
20 stylez,
21 ]}
22 />
23 );
24}
25
26

Explanation:

  • useAnimatedStyle: The width of the indicator is animated as the user switches between onboarding steps. This gives a fluid sliding effect over the dots.
  • Layout: The indicator has a fixed position and moves between the dots.

Step 5: Assembling the Pagination Component

Now, let’s bring the Dot and PaginationIndicator components together into a Pagination component. This component will render the dots and control the active step.

Pagination Component

1export function Pagination({ selectedIndex, total }: { selectedIndex: number; total: number; }) {
2 const animation = useDerivedValue(() => {
3 return withSpring(selectedIndex, { damping: 80, stiffness: 200 });
4 });
5
6 return (
7 <View style={{ justifyContent: "center", alignItems: "center" }}>
8 <View style={{ flexDirection: "row" }}>
9 <PaginationIndicator animation={animation} />
10 {[...Array(total).keys()].map((i) => (
11 <Dot key={`dot-${i}`} index={i} animation={animation} />
12 ))}
13 </View>
14 </View>
15 );
16}
17
18

Explanation:

  • selectedIndex: The selectedIndex determines the current step, which controls the dot color and indicator animation.
  • useDerivedValue: We use useDerivedValue to smoothly animate the transitions between steps with a spring effect.
  • Rendering Dots: The dots are rendered dynamically based on the total number of steps in the onboarding flow.

Step 6: Building the Main Onboarding Flow

Finally, we’ll put everything together into the main Onboarding component, which manages the steps, pagination, and buttons for navigating through the onboarding flow.

Onboarding Component

1export function Onboarding({ total, selectedIndex, onIndexChange }: { total: number; selectedIndex: number; onIndexChange: (index: number) => void; }) {
2 return (
3 <View style={{ padding: _spacing, gap: _spacing * 2 }}>
4 <Pagination total={total} selectedIndex={selectedIndex} />
5 <View style={{ flexDirection: "row", gap: _spacing }}>
6 {selectedIndex > 0 && (
7 <Button
8 style={{ backgroundColor: "#ddd" }}
9 onPress={() => { onIndexChange(selectedIndex - 1); }}
10 >
11 <Text style={{ fontWeight: "700" }}>Back</Text>
12 </Button>
13 )}
14 <Button
15 style={{ backgroundColor: "#036BFB", flex: 1 }}
16 onPress={() => {
17 if (selectedIndex >= total - 1) return;
18 onIndexChange(selectedIndex + 1);
19 }}
20 >
21 {selectedIndex === total - 1 ? (
22 <Animated.Text
23 key="finish"
24 style={{ color: "#fff", fontWeight: "700" }}
25 entering={FadeInDown.springify().damping(80).stiffness(200)}
26 exiting={FadeOutUp.springify().damping(80).stiffness(200)}
27 >
28 Finish
29 </Animated.Text>
30 ) : (
31 <Animated.Text
32 key="continue"
33 style={{ color: "#fff", fontWeight: "700" }}
34 layout={_layoutTransition}
35 entering={FadeInDown.springify().damping(80).stiffness(200)}
36 exiting={FadeOutUp.springify().damping(80).stiffness(200)}
37 >
38 Continue
39 </Animated.Text>
40 )}
41 </Button>
42 </View>
43 </View>
44 );
45}
46
47

Explanation:

  • Buttons: If the selectedIndex is greater than 0, the “Back” button appears, allowing the user to go to the previous step. The “Continue” button dynamically changes to “Finish” on the last step.
  • Pagination: The current index is passed to the Pagination component, which updates the dot colors and indicator position.

Step 7: Integrating the Onboarding Component into a Screen

To use the Onboarding component in your app, you can integrate it within a screen like this:

1export default function OnboardingScreen() {
2 const [index, setIndex] = useState(0);
3 const totalSteps = 3;
4
5 return (
6 <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
7 <Onboarding
8 total={totalSteps}
9 selectedIndex={index}
10 onIndexChange={setIndex}
11 />
12 </View>
13 );
14}
15
16

Explanation:

  • Managing State: We manage the current step using the index state. The total number of steps is set to 3, and we pass this to the Onboarding component.
  • Handling Navigation: The setIndex function updates the current step, allowing the user to navigate through the onboarding steps.

Conclusion

By following these steps, you’ll be able to create a fully functional animated onboarding flow using React Native Reanimated. The components are modular and can be easily customized to fit your app's design. You can adjust the animations, layout, and overall behavior to provide a unique onboarding experience for your users.

Grab the code

loading...

Let me know if you need any more assistance!