Creating a dynamic user experience in mobile applications often involves making components react to the user’s scroll behavior. From hiding headers when scrolling down to providing a floating scroll-to-top button and applying smooth fade effects, these interactions enhance usability and create a modern feel.
In this guide, we’ll walk through building a centralized scroll management system in React Native using FlatList
, react-native-reanimated
, and context. We’ll implement the following features:
- An animated header that hides on scroll.
- A floating scroll-to-top button that appears when the user scrolls down.
Why Use a Shared Scroll Context?
Using a shared scroll context allows us to:
- Manage scroll position centrally, making it accessible to any component that needs it.
- Animate multiple components in sync based on scroll events.
- Easily implement a scroll-to-top functionality that can be triggered from any part of the app.
Required Libraries
- React Native Reanimated: Provides efficient and smooth animations.
- React Context: Allows us to share the scroll state across components.
Step 1: Setting Up the Shared Scroll Context
We’ll start by creating a SharedStateContext
to store two values:
-
scrollY
: Tracks whether the user is scrolling down. -
scrollYGlobal
: Tracks the overall scroll position for global animations.
Creating the Shared Context
import React, { createContext, useContext } from 'react';
import { useSharedValue, withTiming } from 'react-native-reanimated';
const SharedStateContext = createContext(undefined);
export const SharedStateProvider = ({ children }) => {
// Create shared values for scroll positions
const scrollY = useSharedValue(0); // Animate scrollY value based on scroll direction
const scrollYGlobal = useSharedValue(0); // Get the current vertical scroll position
// Function to scroll to the top
const scrollToTop = React.useCallback(() => {
scrollY.value = withTiming(0, { duration: 300 }); // Animate scrollY to 0 over 300ms
scrollYGlobal.value = withTiming(0, { duration: 300 }); // Animate scrollYGlobal to 0 over 300ms
}, [scrollY, scrollYGlobal]);
// Memoize the context value to avoid unnecessary re-renders
const value = React.useMemo(
() => ({ scrollToTop, scrollY, scrollYGlobal }),
[scrollY, scrollYGlobal, scrollToTop]
);
return (
// Provide the shared state to children components
<SharedStateContext.Provider value={value}>{children}</SharedStateContext.Provider>
);
};
export const useSharedState = () => {
const context = useContext(SharedStateContext);
if (context === undefined) {
throw new Error('useSharedState must be used within a SharedStateProvider');
}
return context;
};
Explanation
-
scrollY
: A shared value that tracks whether the user is scrolling down (1) or up (0), based on scroll events. -
scrollYGlobal
: Tracks the overall scroll position of ourFlatList
. -
scrollToTop
: Resets bothscrollY
andscrollYGlobal
values to zero, providing a smooth scroll-to-top action.
Wrapping the App with SharedStateProvider
Wrap your main app component (or specific screen components) with SharedStateProvider
to make scrollY
, scrollYGlobal
, and scrollToTop
accessible across components.
import React from 'react';
import MainScreen from './MainScreen';
import { SharedStateProvider } from './SharedContext';
const App = () => (
<SharedStateProvider>
<MainScreen />
</SharedStateProvider>
);
export default App;
Step 2: Creating an Animated Header Component
The Header
component will slide up and hide when the user scrolls down and reappear when scrolling up. This behavior is controlled by scrollY
, which detects the scroll direction.
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import Animated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
import { useSharedState } from './SharedContext';
const Header = () => {
const { scrollY } = useSharedState();
const animatedHeaderStyle = useAnimatedStyle(() => ({
transform: [
{
translateY:
scrollY.value === 1
? withTiming(-100, { duration: 300 })
: withTiming(0, { duration: 300 }),
},
],
}));
return (
<Animated.View style={[styles.header, animatedHeaderStyle]}>
<Text style={styles.headerText}>Animated Header</Text>
</Animated.View>
);
};
export default Header;
const styles = StyleSheet.create({
header: {
paddingTop: 40,
height: 100,
backgroundColor: '#6200ee',
justifyContent: 'center',
alignItems: 'center',
position: 'absolute',
width: '100%',
top: 0,
zIndex: 1,
},
headerText: {
color: '#fff',
fontSize: 18,
},
});
Explanation
-
translateY
animation: Controls the position of the header based onscrollY
. WhenscrollY
is 1 (scrolling down), the header slides up out of view; otherwise, it remains visible. -
withTiming
: Smoothly animates the transition, providing a polished user experience.
Step 3: Implementing the Scroll-To-Top Feature with FlatList
Next, we’ll set up MainScreen
with a FlatList
, Header
, and a floating scroll-to-top button. This screen will track the scroll position and trigger animations when reaches 100px during scroll.
MainScreen Component
import React, { useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, { withTiming } from 'react-native-reanimated';
import Header from './Header';
import ScrollToTopButton from './ScrollToTopButton';
import { useSharedState } from './SharedContext';
const MainScreen = () => {
const { scrollY, scrollYGlobal, scrollToTop } = useSharedState();
const flatListRef = useRef(null);
const handleScroll = (event) => {
const currentScrollY = event.nativeEvent.contentOffset.y;
const isScrollingDown = currentScrollY > 50;
scrollY.value = isScrollingDown
? withTiming(1, { duration: 300 })
: withTiming(0, { duration: 300 });
scrollYGlobal.value = currentScrollY;
};
const handleScrollToTop = () => {
scrollToTop();
flatListRef.current?.scrollToOffset({
offset: 0,
animated: true,
});
};
const data = Array.from({ length: 50 }, (_, i) => ({ id: String(i), text: `Item ${i + 1}` }));
return (
<View style={styles.container}>
<Header />
<Animated.FlatList
ref={flatListRef}
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<View style={styles.item}>
<Text>{item.text}</Text>
</View>
)}
onScroll={handleScroll}
scrollEventThrottle={16}
contentContainerStyle={{ paddingTop: 100 }}
/>
<ScrollToTopButton onPress={handleScrollToTop} />
</View>
);
};
export default MainScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
item: {
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#ccc',
},
});
Step 4: Floating Scroll-To-Top Button with Opacity Control
The ScrollToTopButton
component only appears when the user scrolls down beyond 100 pixels. Its opacity fades in, adding a subtle yet dynamic touch to the UI.
import React from 'react';
import { StyleSheet, Text, TouchableOpacity } from 'react-native';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
import { useSharedState } from './SharedContext';
const ScrollToTopButton = ({ onPress }) => {
const { scrollYGlobal } = useSharedState();
const animatedStyle = useAnimatedStyle(() => ({
opacity: scrollYGlobal.value > 100 ? 1 : 0,
transform: [{ translateY: scrollYGlobal.value > 100 ? 0 : 100 }],
}));
return (
<Animated.View style={[styles.buttonContainer, animatedStyle]}>
<TouchableOpacity onPress={onPress} style={styles.button}>
<Text style={styles.buttonText}>Top</Text>
</TouchableOpacity>
</Animated.View>
);
};
export default ScrollToTopButton;
const styles = StyleSheet.create({
buttonContainer: {
position: 'absolute',
bottom: 30,
right: 20,
},
button: {
backgroundColor: '#6200ee',
padding: 20,
borderRadius: 30,
},
buttonText: {
color: '#fff',
fontSize: 16,
},
});
Final Thoughts
In this tutorial, we created an advanced scroll management system with a dynamic React Native UI that includes:
- An animated header that hides when the user scrolls down.
- A floating scroll-to-top button that fades in after scrolling down.
Using react-native-reanimated
and context, this setup allows you to manage scroll interactions and animations centrally, providing a seamless and responsive user experience. This approach is perfect for building polished, modern UIs that respond intuitively to user interactions.
Top comments (0)