ExpoStartup

ImageCarousel

A customizable image carousel component for displaying multiple images in a scrollable horizontal layout.

ImageCarousel

The ImageCarousel component provides a horizontal scrollable image gallery with pagination indicators. It's perfect for showcasing multiple images in a compact space, with support for interactivity, automatic playback, and customizable styling.

Features

  • Horizontal Image Scrolling: Display multiple images in a swipeable carousel
  • Pagination Indicators: Optional dot or numeric indicators for the current image
  • Custom Sizing: Control width and height of the carousel
  • Image Interaction: Support for handling image tap events
  • Rounded Corners: Customizable border radius options
  • Auto-Play Support: Optional automatic image cycling
  • Looping Support: Seamless looping through images

Import

import ImageCarousel from '@/components/ImageCarousel';

Props

PropTypeDefaultDescription
imagesstring[] | ImageSourcePropType[]RequiredArray of image URLs or source objects
widthnumberScreen widthWidth of the carousel
heightnumber200Height of the carousel
showPaginationbooleantrueWhether to show pagination indicators
paginationStyle'dots' | 'numbers''dots'Style of pagination indicators
onImagePress(index: number) => voidFunction to call when an image is pressed
autoPlaybooleanfalseWhether to automatically cycle through images
autoPlayIntervalnumber3000Time in milliseconds between auto-play transitions
loopbooleantrueWhether to loop back to the first image after the last one
classNamestring''Additional Tailwind classes
rounded'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full''none'Border radius of the carousel

Usage Examples

const images = [
  'https://example.com/image1.jpg',
  'https://example.com/image2.jpg',
  'https://example.com/image3.jpg',
];
 
<ImageCarousel 
  images={images} 
  height={200} 
/>
<ImageCarousel 
  images={images} 
  height={250} 
  paginationStyle="numbers" 
/>
<ImageCarousel 
  images={images} 
  height={180} 
  rounded="xl" 
  onImagePress={(index) => {
    console.log(`Image ${index} pressed`);
    // Open fullscreen viewer or navigate to detail page
  }}
/>
<ImageCarousel 
  images={images} 
  height={300} 
  autoPlay={true} 
  autoPlayInterval={5000} 
/>

Custom Styling

<ImageCarousel 
  images={images} 
  height={220} 
  rounded="lg" 
  className="shadow-lg" 
/>

Integration Examples

function ProductGallery({ product }) {
  return (
    <View className="flex-1">
      <ImageCarousel 
        images={product.images} 
        height={300} 
        rounded="md" 
        onImagePress={(index) => openFullscreen(product.images, index)}
      />
      
      <View className="p-4">
        <Text className="text-xl font-bold">{product.name}</Text>
        <Text className="text-lg font-bold text-blue-500">${product.price}</Text>
        <Text className="mt-2">{product.description}</Text>
        
        {/* Product details and actions */}
      </View>
    </View>
  );
}
function HomeScreen() {
  const featuredItems = [
    { id: '1', image: 'https://example.com/feature1.jpg', title: 'Summer Sale' },
    { id: '2', image: 'https://example.com/feature2.jpg', title: 'New Arrivals' },
    { id: '3', image: 'https://example.com/feature3.jpg', title: 'Limited Edition' },
  ];
  
  const handleItemPress = (index) => {
    // Navigate to the selected featured item
    router.navigate(`/promotions/${featuredItems[index].id}`);
  };
  
  return (
    <ScrollView className="flex-1">
      <View className="mb-4">
        <ImageCarousel 
          images={featuredItems.map(item => item.image)} 
          height={200} 
          autoPlay={true} 
          rounded="lg" 
          onImagePress={handleItemPress}
        />
      </View>
      
      {/* Rest of the home screen content */}
    </ScrollView>
  );
}

Property Listing Images

function PropertyListing({ property }) {
  const [fullscreenVisible, setFullscreenVisible] = useState(false);
  const [initialIndex, setInitialIndex] = useState(0);
  
  const openFullscreenGallery = (index) => {
    setInitialIndex(index);
    setFullscreenVisible(true);
  };
  
  return (
    <View className="flex-1">
      <ImageCarousel 
        images={property.images} 
        height={250} 
        paginationStyle="numbers" 
        onImagePress={openFullscreenGallery}
      />
      
      <View className="p-4">
        <Text className="text-2xl font-bold">{property.title}</Text>
        <Text className="text-lg">{property.location}</Text>
        <Text className="text-xl font-bold text-green-600 mt-2">${property.price}</Text>
        
        {/* Property details */}
      </View>
      
      {fullscreenVisible && (
        <FullscreenGallery 
          images={property.images}
          initialIndex={initialIndex}
          onClose={() => setFullscreenVisible(false)}
        />
      )}
    </View>
  );
}

News Article Carousel

function NewsCarousel({ articles }) {
  const navigation = useNavigation();
  
  return (
    <View className="mb-6">
      <Text className="font-bold text-xl mb-2 px-4">Top Stories</Text>
      
      <ImageCarousel 
        images={articles.map(article => article.coverImage)} 
        height={180} 
        onImagePress={(index) => {
          navigation.navigate('ArticleDetail', { id: articles[index].id });
        }}
        autoPlay={true}
        rounded="md"
      />
      
      <View className="absolute bottom-0 left-0 right-0 bg-black/50 py-2 px-4">
        <Text className="text-white font-bold">{articles[0].title}</Text>
      </View>
    </View>
  );
}

Custom Implementations

function CarouselWithOverlay({ images, titles }) {
  const [activeIndex, setActiveIndex] = useState(0);
  
  const handleImageChange = (index) => {
    setActiveIndex(index);
  };
  
  return (
    <View className="relative">
      <ImageCarousel 
        images={images} 
        height={250} 
        showPagination={false}
        onImagePress={(index) => console.log(`Image ${index} pressed`)}
        rounded="lg"
      />
      
      <View className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4">
        <Text className="text-white text-xl font-bold">{titles[activeIndex]}</Text>
        
        <View className="flex-row mt-2">
          {images.map((_, index) => (
            <View
              key={index}
              className={`h-1 mr-1 rounded-full ${
                index === activeIndex ? 'bg-white w-8' : 'bg-white/40 w-4'
              }`}
            />
          ))}
        </View>
      </View>
    </View>
  );
}
function CarouselWithThumbnails({ images }) {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <View>
      <ImageCarousel 
        images={images} 
        height={300} 
        showPagination={false}
        rounded="md"
      />
      
      <ScrollView 
        horizontal 
        showsHorizontalScrollIndicator={false} 
        className="mt-2"
      >
        {images.map((image, index) => (
          <Pressable 
            key={index}
            onPress={() => setActiveIndex(index)}
            className={`mr-2 ${activeIndex === index ? 'border-2 border-blue-500' : ''}`}
          >
            <Image 
              source={{ uri: image }} 
              className="w-16 h-16 rounded"
              resizeMode="cover"
            />
          </Pressable>
        ))}
      </ScrollView>
    </View>
  );
}

Best Practices

Optimize Image Loading

Ensure images are properly optimized for carousel display:

// Optimize images for carousel display
const optimizedImages = [
  { uri: 'https://example.com/image1.jpg', width: 800, height: 600 },
  { uri: 'https://example.com/image2.jpg', width: 800, height: 600 },
  { uri: 'https://example.com/image3.jpg', width: 800, height: 600 }
];
 
<ImageCarousel 
  images={optimizedImages} 
  height={200}
  // Other props 
/>

Handle Image Load Errors

Implement fallback mechanisms for image loading failures:

function CarouselWithFallbacks() {
  // Process images to include fallbacks
  const processedImages = rawImages.map(img => {
    // For remote URLs, provide a fallback
    if (typeof img === 'string') {
      return { 
        uri: img, 
        // These props are passed to Image component
        defaultSource: require('../assets/placeholder.png'),
        onError: (e) => console.log(`Failed to load: ${img}`, e.nativeEvent.error) 
      };
    }
    return img;
  });
  
  return (
    <ImageCarousel 
      images={processedImages} 
      height={220}
    />
  );
}

Adjust carousel height based on screen dimensions:

function ResponsiveCarousel() {
  const { width } = useWindowDimensions();
  const carouselHeight = width * 0.75; // Maintain 4:3 aspect ratio
  
  return (
    <ImageCarousel 
      images={images} 
      height={carouselHeight} 
      // Other props
    />
  );
}

Implementation Details

The ImageCarousel component is built using React Native's FlatList component with horizontal scrolling and paging enabled. It handles layout measurement to adapt to container width when a specific width is not provided.

The pagination indicators are rendered as either dots (small circles that highlight the current image) or a numeric display (showing current position and total count). These are positioned absolutely at the bottom of the carousel.

The component uses the onMomentumScrollEnd event from FlatList to detect when the user has settled on a new image, which updates the active index state. This drives the pagination indicator updates.

Each image is rendered within a Pressable component to enable the onImagePress callback, allowing for interaction with individual carousel items.

Notes

  • The FlatList used internally has pagingEnabled set to true for snap behavior
  • For best performance, provide appropriately sized images to avoid excessive memory usage
  • When using autoPlay, the carousel will pause when the user interacts with it
  • The component automatically adapts to container width if no specific width is provided
  • Pagination indicators are rendered on top of images using absolute positioning