High-performance PagerView component for React Native, built on react-native-reanimated v4
npm install react-native-reanimated-pager-viewHigh-performance PagerView component for React Native, built on react-native-reanimated v4 and react-native-gesture-handler. Enhanced customization capabilities and smooth animations running on the native thread.
- 🚀 High Performance - uses react-native-reanimated v4 for smooth animations on the native thread
- 🎨 Full Customization - configure gestures, animations and behavior through callbacks
- ✨ Custom Page Animations - create stunning page transitions with pageStyleInterpolator
- ↔️ Overscroll effects - add an overscroll effect when reaching the edges (see createBounceScrollOffsetInterpolator for iOS-like bounce effect)
- 📱 Platform Support - iOS and Android
- 🔧 TypeScript - complete type safety out of the box
- 🎯 Lazy loading - deferred page loading for performance optimization
- 👀 Visibility tracking - track visible pages on screen
- 🔄 Dynamic management - add/remove pages with automatic positioning
- 📱 Vertical Mode - support for vertical scrolling
https://github.com/user-attachments/assets/121e4339-e74d-4946-8d73-4760cc221d34
- 📚 API Documentation
- Basic Properties
- Animation Customization
- Callbacks
- Gesture Customization
- Performance
- Page Management
- Ref Methods
- ↔️ Scroll Offset Interpolation
- 💄 Dynamic Styling
- 🔧 ScrollableWrapper Component
- 👀 Page Visibility Tracking
- 📱 Vertical Mode
- 🎯 Advanced Examples
- Custom Page Animations
- Lazy Loading
- Gesture Customization
- 🔧 React Navigation Integration
- ⚡ Performance Optimization
``bashnpm
npm install react-native-reanimated-pager-view react-native-reanimated react-native-gesture-handler
Follow the installation instructions for react-native-reanimated and react-native-gesture-handler.
$3
`tsx
import React, { useCallback, useMemo } from 'react';
import { View, Text } from 'react-native';
import {
PagerView,
createBounceScrollOffsetInterpolator,
} from 'react-native-reanimated-pager-view';const pages = [
{ id: 'page1', color: '#ff6b6b', title: 'Page 1' },
{ id: 'page2', color: '#4ecdc4', title: 'Page 2' },
{ id: 'page3', color: '#45b7d1', title: 'Page 3' },
];
// Optional: Add bounce effect
const bounceInterpolator = createBounceScrollOffsetInterpolator();
export default function App() {
const children = useMemo(
() =>
pages.map((page) => (
{page.title}
)),
[],
);
return (
{children}
);
}
`$3
When using scrollable components inside PagerView pages, you need to prevent gesture conflicts. The library provides a
ScrollableWrapper component that automatically handles this.📚 API Documentation
$3
| Property | Type | Default | Description |
| --------------- | ---------------------------- | -------------- | -------------------------------------------- |
|
children | ReactNode[] | - | Array of pages to display |
| style | StyleProp | - | Style object/array or function for the container |
| initialPage | number | 0 | Initial page number |
| scrollEnabled | boolean | true | Enable pager scrolling |
| pageMargin | number | 0 | Margin between pages |
| orientation | 'horizontal' \| 'vertical' | 'horizontal' | Scrolling direction (horizontal or vertical) |$3
| Property | Type | Default | Description |
| -------------------------- | -------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
|
pageStyleInterpolator | PageStyleInterpolator | - | Custom function for animating pages based on scroll position (must be a worklet) |
| scrollOffsetInterpolator | ScrollOffsetInterpolator | - | Custom function for modifying scroll behavior and overscroll effects |
| scrollToPageSpringConfig | ScrollToPageSpringConfig | - | Configure spring parameters used when scrolling to the target page after a drag or setPage (must be a worklet) |$3
Note: Only
onPageScroll should be a worklet for optimal performance. All other callbacks are called via runOnJS.| Property | Type | Description |
| -------------------------- | ---------------------------- | ------------------------------------------------------------------------ |
|
onPageSelected | (page: number) => void | Called when a new page is selected |
| onPageScroll | (position: number) => void | Called during scrolling with current scroll position (should be worklet) |
| onPageScrollStateChanged | (state) => void | State change during scrolling |
| onDragStart | () => void | Start of drag gesture |
| onDragEnd | () => void | End of drag gesture |
| onInitialMeasure | () => void | Called after initial measurement of container dimensions |$3
| Property | Type | Default | Description |
| ---------------------------------------- | ------------------------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
gestureConfiguration | (gesture: Gesture) => Gesture | - | Function to customize pan gesture |
| activationDistance | number | 10 | Minimum distance before activation |
| gestureDirectionToleranceDeg | number | 45 | Maximum diagonal angle (in degrees) allowed between swipe and pager axis before PagerView rejects the gesture. Increase to allow more diagonal swipes; decrease to require straighter swipes. |
| failActivationWhenExceedingStartEdge | boolean | false | Fail gesture activation when swiping beyond start edge. For example, this is useful for resolving conflicts with fullscreen swipe-back navigation gesture |
| failActivationWhenExceedingEndEdge | boolean | false | Fail gesture activation when swiping beyond end edge. Allows parent gestures to handle |
| hitSlop | HitSlop | - | Define touchable area for gesture recognition |
| blockParentScrollableWrapperActivation | boolean (iOS) | false; iOS only | When true, PagerView's pan gesture actively blocks activation of the nearest parent ScrollableWrapper gesture (if any). Use to capture swipes while parent scrollable is still decelerating (momentum) |
| blocksExternalGesture | ExternalGesture[] | - | Inverse (many-to-one) relation of requireExternalGestureToFail. Prevents listed gestures from activating simultaneously; PagerView gesture takes priority |
| panVelocityThreshold | number | 500 | Minimum velocity for page switching. Note: page will switch if scrolled past 50% regardless of velocity |
| pageActivationThreshold | number | 0.8 | Visibility percentage for page activation |$3
| Property | Type | Default | Description |
| -------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------ |
|
lazy | boolean | false | Deferred page loading |
| lazyPageLimit | number | 1 | Number of pages to preload |
| removeClippedPages | boolean | true | Remove invisible pages from Native Tree. Note: enabled by default but may cause issues, use with caution ⚠️. |$3
| Property | Type | Default | Description |
| --------------------------------- | ---------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
holdCurrentPageOnChildrenUpdate | boolean | false | Maintain current page position when children array changes. Useful when pages are dynamically added/removed and you want to stay on the same logical page |
| estimatedSize | number \| null | Dimensions.get('window').width | Expected container size (width for horizontal, height for vertical orientation). If null, size will be measured on first render (may cause layout shift) |$3
| Method | Type | Description |
| ------------------------- | ------------------------ | ---------------------------------- |
|
setPage | (page: number) => void | Navigate to page with animation |
| setPageWithoutAnimation | (page: number) => void | Navigate to page without animation |↔️ Scroll Offset Interpolation
The library provides a built-in
createBounceScrollOffsetInterpolator utility for creating iOS-like bounce effects:`tsx
import { createBounceScrollOffsetInterpolator } from 'react-native-reanimated-pager-view';const bounceInterpolator = createBounceScrollOffsetInterpolator({
resistanceFactor: 0.7, // Overscroll resistance (0-1)
threshold: 0.3, // Threshold for callback trigger
onThresholdReached: ({ side }) => {
console.log(
Reached ${side} boundary);
},
triggerThresholdCallbackOnlyOnce: false, // Trigger callback multiple times or once per gesture
}); ;
`> You can write your own scroll offset interpolator by following the same pattern as
createBounceScrollOffsetInterpolator 🔥.💄 Dynamic Styling
The
style prop can accept a function for dynamic styling based on scroll position:`tsx
const rubberBandStyle: PagerStyleFn = ({ scrollPosition }) => {
'worklet'; return {
transformOrigin: scrollPosition < 0 ? 'top' : 'bottom',
transform: [
{
scaleY: interpolate(
scrollPosition,
[-1, 0, pages.length - 1, pages.length],
[1.25, 1, 1, 1.25],
),
},
],
};
};
;
`🔧 ScrollableWrapper Component
The
ScrollableWrapper component solves gesture conflicts when using scrollable components inside PagerView pages. It automatically detects the PagerView's orientation and configures gesture priorities to prevent conflicts. Also, it provides Twitter/𝕏-like behavior where swiping to change pages smoothly interrupts internal scrolling and switches to PagerView gesture (just wrap everything that can scroll 🪄).$3
Use
ScrollableWrapper whenever you have scrollable content inside PagerView pages:- Vertical scrolls inside horizontal PagerView (FlatList, ScrollView, etc.)
- Horizontal scrolls inside vertical PagerView (horizontal FlatList, carousel, etc.)
- Any scrollable component that might conflict with PagerView gestures
$3
`tsx
import { ScrollableWrapper } from 'react-native-reanimated-pager-view';// For vertical scrolling content in horizontal PagerView
function FeedPage() {
return (
data={posts}
renderItem={({ item }) => }
/>
);
}
// For horizontal scrolling content in vertical PagerView
function HorizontalCarouselPage() {
return (
data={items}
horizontal
renderItem={({ item }) => }
/>
);
}
`$3
`tsx
function ComplexPage() {
return (
{/ Vertical main content /}
Main content that scrolls vertically {/ Horizontal carousel within vertical scroll /}
data={carouselItems}
horizontal
renderItem={({ item }) => }
/>
More vertical content...
);
}
`👀 Page Visibility Tracking
The library provides built-in support for tracking which pages are currently visible on screen. This is useful for analytics, lazy loading content, pausing/resuming videos, or any other visibility-dependent features.
`tsx
import React, { useMemo } from 'react';
import { useIsOnscreenPage } from 'react-native-reanimated-pager-view';const VISIBILITY_PAGES = [
{ id: 'page1', title: 'Page 1' },
{ id: 'page2', title: 'Page 2' },
{ id: 'page3', title: 'Page 3' },
];
const PageWithTracking = ({ data }) => {
const isOnscreen = useIsOnscreenPage();
return (
{data.title}
Visible: {isOnscreen ? 'Yes' : 'No'}
);
};
const VisibilityTrackingExample = () => {
const children = useMemo(
() =>
VISIBILITY_PAGES.map((page) => (
)),
[],
);
return {children} ;
};
`📱 Vertical Mode
The PagerView supports vertical scrolling, perfect for creating any vertical page-based navigation.
`tsx
import React, { useMemo } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { PagerView } from 'react-native-reanimated-pager-view';const pages = [
{ id: 'page1', color: '#ff6b6b', title: 'Swipe Up' },
{ id: 'page2', color: '#4ecdc4', title: 'Keep Swiping' },
{ id: 'page3', color: '#45b7d1', title: 'Almost There' },
{ id: 'page4', color: '#96ceb4', title: 'Last Page!' },
];
// Create bounce interpolator for vertical mode
const bounceInterpolator = createBounceScrollOffsetInterpolator();
const VerticalExample = () => {
const children = useMemo(
() =>
pages.map((page) => (
key={page.id}
style={[styles.page, { backgroundColor: page.color }]}
>
{page.title}
)),
[],
);
return (
style={styles.container}
orientation="vertical"
scrollOffsetInterpolator={bounceInterpolator}
>
{children}
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
page: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: 'white',
},
});
`🎯 Advanced Examples
$3
`tsx
import React, { useMemo } from 'react';
import { interpolate } from 'react-native-reanimated';
import {
PagerView,
type PageStyleInterpolator,
} from 'react-native-reanimated-pager-view';const ANIMATION_PAGES = [
{ id: 'page1', title: 'Page 1', color: '#ff6b6b' },
{ id: 'page2', title: 'Page 2', color: '#4ecdc4' },
{ id: 'page3', title: 'Page 3', color: '#45b7d1' },
];
const pageStyleInterpolator: PageStyleInterpolator = ({ pageOffset }) => {
'worklet';
const rotateY = interpolate(pageOffset, [-1, 0, 1], [60, 0, -60], 'clamp');
const scale = interpolate(pageOffset, [-1, 0, 1], [0.8, 1, 0.8], 'clamp');
return {
transform: [{ perspective: 1000 }, { rotateY:
${rotateY}deg }, { scale }],
};
};const CustomAnimationPager = () => {
const children = useMemo(
() =>
ANIMATION_PAGES.map((page) => (
key={page.id}
style={{
flex: 1,
backgroundColor: page.color,
justifyContent: 'center',
alignItems: 'center',
}}
>
{page.title}
)),
[],
);
return (
{children}
);
};
`$3
`tsx
import React, { useMemo } from 'react';const LAZY_PAGES = [
{ id: 'page1', title: 'Page 1', content: 'Heavy content 1' },
{ id: 'page2', title: 'Page 2', content: 'Heavy content 2' },
{ id: 'page3', title: 'Page 3', content: 'Heavy content 3' },
];
const LazyPagerExample = () => {
const children = useMemo(
() => LAZY_PAGES.map((page) => ),
[],
);
return (
{children}
);
};
`$3
`tsx
import React, { useCallback, useMemo } from 'react';const GESTURE_PAGES = [
{ id: 'page1', title: 'Page 1' },
{ id: 'page2', title: 'Page 2' },
{ id: 'page3', title: 'Page 3' },
];
const customGestureConfig = (gesture: Gesture) => {
// Reduce gesture area
return gesture.hitSlop(-10);
};
const CustomGesturePager = () => {
const children = useMemo(
() =>
GESTURE_PAGES.map((page) => (
{page.title}
)),
[],
);
return (
{children}
);
};
`🔧 React Navigation Integration
When using swipe-back navigation gesture, PagerView can interfere by capturing swipes from the screen edge. These props solve the conflict:
- Use
failActivationWhenExceedingStartEdge={true} when you want to allow fullscreen swipe-back gesture from first page (you should enable fullscreen swipe-back gesture in your navigation configuration too).
- Use hitSlop to add area on the left edge of PagerView that would allow swipe-back gesture to be recognized. You probably want this value to be equal to the value of React Navigation's gestureResponseDistance prop.`tsx
import React, { useMemo } from 'react';
import { View, Text } from 'react-native';
import { PagerView } from 'react-native-reanimated-pager-view';const pages = [
{ id: 'page1', color: '#ff6b6b', title: 'Page 1' },
{ id: 'page2', color: '#4ecdc4', title: 'Page 2' },
{ id: 'page3', color: '#45b7d1', title: 'Page 3' },
];
// Add 50px of non-swipeable area on the left edge
const pagerViewSwipeBackArea = { left: -50 };
export default function ScreenInStack() {
const children = useMemo(
() =>
pages.map((page) => (
{page.title}
)),
[],
);
return (
failActivationWhenExceedingStartEdge
hitSlop={pagerViewSwipeBackArea}
>
{children}
);
}
`That's it! These two props ensure your PagerView works seamlessly with navigation swipe-back gesture.
> You can use the same approach for modals with vertical swipes too! 🔥
⚡ Performance Optimization
$3
1. Use lazy loading for large number of pages
2. Optimize children - use key props, memo, and consider wrapping all children in useMemo or extracting outside component
3. Be careful with removeClippedPages - while enabled by default for memory optimization, it may cause issues with complex layouts. Disable if you encounter problems, but expect potential performance impact
$3
`tsx
// Extract pages outside component to avoid recreation
const STATIC_PAGES = [
{ id: 'page1', title: 'Page 1', color: '#ff6b6b' },
{ id: 'page2', title: 'Page 2', color: '#4ecdc4' },
{ id: 'page3', title: 'Page 3', color: '#45b7d1' },
];const MemoizedPage = memo(({ data }) => (
{data.title}
));
const OptimizedPager = memo(() => {
// Memoize children to prevent recreation on re-renders
const children = useMemo(
() =>
STATIC_PAGES.map((page) => ),
[],
);
// Memoize callbacks
const handlePageSelected = useCallback((page: number) => {
console.log('Page selected:', page);
}, []);
return (
lazy={true}
lazyPageLimit={1}
removeClippedPages={true}
estimatedSize={screenWidth}
onPageSelected={handlePageSelected}
>
{children}
);
});
``- react-native-reanimated - for the powerful animation engine
- react-native-gesture-handler - for the flexible gesture system
- react-native-pager-view - for inspiration and API reference
---
Made with ❤️ for the React Native community