I will walk you through a React Native CarouselComponent
that handles dynamic data and autoplay functionality. This component also incorporates smooth pagination, user interactions, and automatic scrolling.
Key Features:
- Auto-scroll with intervals and dynamic offset control
- Pagination that reflects the active slide
- Manual interruption of auto-scroll by user interaction
- Dynamic styling for each carousel item
The Component Breakdown
1. Core Libraries
We are leveraging several key libraries to build this carousel:
-
react-native-reanimated
: Provides performant animations and scroll handling. -
react-native
: Provides core components likeFlatList
,StyleSheet
, andView
.
2. Shared State and Scroll Handling
The component uses shared values (useSharedValue
) for tracking the scroll offset (x
and offset
). useAnimatedScrollHandler
is employed to update the x
value dynamically as the user scrolls through the carousel.
const x = useSharedValue(0);
const offset = useSharedValue(0);
const onScroll = useAnimatedScrollHandler({
onScroll: (e) => {
x.value = e.contentOffset.x;
},
onMomentumEnd: (e) => {
offset.value = e.contentOffset.x;
},
});
-
Autoplay Functionality
The carousel automatically scrolls through items with an interval. If the user interacts with the carousel by swiping, the autoplay is paused, and it resumes once scrolling ends.
useEffect(() => {
if (isAutoPlay) {
interval.current = setInterval(() => {
if (offset.value >= (dataList.length - 1) * width) {
offset.value = 0; // Loop back to the start
} else {
offset.value += width;
}
}, 1000);
} else {
clearInterval(interval.current);
}
return () => clearInterval(interval.current);
}, [isAutoPlay, offset, width, dataList.length]);
-
Pagination
Pagination is a simple set of dots that updates based on the current item being viewed. The active dot gets a more prominent style.
const Pagination = ({ data, paginationIndex }) => {
return (
<View style={styles.paginationContainer}>
{data.map((_, index) => (
<View key={index} style={paginationIndex === index ? styles.activeDot : styles.inactiveDot} />
))}
</View>
);
};
-
Dynamic Carousel Item Rendering
Each carousel item is rendered within a custom AnimatedScalePressView
component that allows scaling when pressed. We also give each item a random background color for a visually distinct appearance.
const renderItem = ({ item, index }) => (
<TouchableOpacity key={index} onPress={() => onPressItem(item)}>
<View
style={{
padding: 2,
height: 150, // Item height value
backgroundColor: getRandomColor(),
width: width - 30, // Item width value
marginHorizontal: 15, //item margin
borderRadius: 10, // Static border radius
}}
/>
</TouchableOpacity>
);
Complete Code
import { StyleSheet, View, useWindowDimensions, TouchableOpacity } from 'react-native';
import React, { useEffect, useRef, useState } from 'react';
import Animated, { scrollTo, useAnimatedRef, useAnimatedScrollHandler, useDerivedValue, useSharedValue } from 'react-native-reanimated';
import AnimatedScalePressView from '../../AnimatedScalePressView';
/*
Usage:
< CarouselComponent data={list||[]} autoPlay onPressItem={onPressItem} />
*/
const Dot = ({ index, paginationIndex }) => {
return <View style={paginationIndex === index ? styles.activeDot : styles.inactiveDot} />;
};
const Pagination = ({ data, paginationIndex }) => {
return (
<View style={styles.paginationContainer}>
{data.map((_, index) => (
<Dot index={index} key={index} paginationIndex={paginationIndex} />
))}
</View>
);
};
const CarouselComponent = ({ data: dataList = [], autoPlay, onPressItem }) => {
const x = useSharedValue(0);
const { width } = useWindowDimensions();
const ref = useAnimatedRef();
const [currentIndex, setCurrentIndex] = useState(0);
const [paginationIndex, setPaginationIndex] = useState(0);
const [isAutoPlay, setIsAutoPlay] = useState(autoPlay);
const offset = useSharedValue(0);
const interval = useRef();
const [data, setData] = useState([...dataList]);
const viewabilityConfig = {
itemVisiblePercentThreshold: 50,
};
const onViewableItemsChanged = ({ viewableItems }) => {
if (viewableItems[0] && viewableItems[0].index !== null) {
setCurrentIndex(viewableItems[0].index);
setPaginationIndex(viewableItems[0].index % dataList.length);
}
};
const viewabilityConfigCallbackPairs = useRef([{ viewabilityConfig, onViewableItemsChanged }]);
const onScroll = useAnimatedScrollHandler({
onScroll: (e) => {
x.value = e.contentOffset.x;
},
onMomentumEnd: (e) => {
offset.value = e.contentOffset.x;
},
});
useDerivedValue(() => {
scrollTo(ref, offset.value, 0, true);
});
useEffect(() => {
if (isAutoPlay) {
interval.current = setInterval(() => {
if (offset.value >= (dataList.length - 1) * width) {
offset.value = 0;
} else {
offset.value += width;
}
}, 1000);
} else {
clearInterval(interval.current);
}
return () => clearInterval(interval.current);
}, [isAutoPlay, offset, width, dataList.length]);
const getRandomColor = () => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
const renderItem = ({ item, index }) => (
<AnimatedScalePressView key={index} onPress={() => onPressItem(item)}>
<View
style={{
padding: 2,
height: 150,
backgroundColor: getRandomColor(),
width: width - 30,
marginHorizontal: 15,
borderRadius: 10,
}}
/>
</AnimatedScalePressView>
);
return (
<View style={styles.container}>
<Animated.FlatList
ref={ref}
style={{ flexGrow: 0 }}
data={data}
horizontal
pagingEnabled
bounces={false}
onScroll={onScroll}
scrollEventThrottle={16}
showsHorizontalScrollIndicator={false}
onScrollBeginDrag={() => setIsAutoPlay(false)}
onMomentumScrollEnd={() => setIsAutoPlay(true)}
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs.current}
onEndReached={() => {
if (data.length === dataList.length * 2) return;
setData((prevData) => [...prevData, ...dataList]);
}}
onEndReachedThreshold={0.5}
keyExtractor={(, index) => `carousel_item${index}`}
renderItem={renderItem}
removeClippedSubviews
/>
{dataList.length > 0 && <Pagination paginationIndex={paginationIndex} data={dataList} />}
</View>
);
};
export default CarouselComponent;
const styles = StyleSheet.create({
container: {
flex: 1,
},
paginationContainer: {
flexDirection: 'row',
height: 30, // gap between pagination dots and list
justifyContent: 'center',
alignItems: 'center',
},
activeDot: {
backgroundColor: '#800020', // Burgundy
height: 8,
width: 30,
marginHorizontal: 2,
borderRadius: 12,
},
inactiveDot: {
backgroundColor: '#D3D3D3', // Light grey
height: 8,
width: 8,
marginHorizontal: 2,
borderRadius: 12,
},
});
Conclusion
This carousel component can easily fit into various React Native projects, providing a smooth scrolling experience with pagination and autoplay.
Top comments (0)