📸 Building an Optimized Image Gallery with Thumbnails in React Native
Amit Kumar

Amit Kumar @amitkumar13

About: Software Engineer

Location:
New Delhi, India
Joined:
Apr 19, 2024

📸 Building an Optimized Image Gallery with Thumbnails in React Native

Publish Date: Feb 23
6 0

In this tutorial, we’ll create a beautiful and optimized image gallery in React Native with a fullscreen preview and a thumbnail navigation system. 🚀

✨ Features

  • Smooth scrolling with horizontal pagination 🔄
  • Thumbnail navigation to jump to a specific image 🔍
  • Optimized performance using useCallback and useRef ⚡
  • Dynamic styling for the active thumbnail 🎨

Let's dive into the implementation! 🏊‍♂️

Image description


🏗️ Setting Up the Component

First, import the necessary modules and initialize the component. We'll use React Native's FlatList, Image, TouchableOpacity, and useWindowDimensions for a responsive design.

import {
  FlatList,
  Image,
  StyleSheet,
  TouchableOpacity,
  useWindowDimensions,
  View,
} from 'react-native';
import React, { useCallback, useRef, useState } from 'react';

  const [activeIndex, setActiveIndex] = useState(0);
  const topRef = useRef(null);
  const thumbRef = useRef(null);

  const {width, height} = useWindowDimensions();
  const IMAGE_SIZE = 80;
  const SPACING = 10;
Enter fullscreen mode Exit fullscreen mode

📌 Core Implementation

🎥 Fullscreen Image Viewer

The FlatList will render the images in full screen, allowing users to swipe through them smoothly.

const fullScreenRenderItem = useCallback(
  ({ item }) => (
    <View style={{ width, height }}>
      <Image source={{ uri: item }} style={styles.fullScreenImage} />
    </View>
  ),
  []
);
Enter fullscreen mode Exit fullscreen mode

🖼️ Thumbnail Navigation

We'll create another FlatList below the fullscreen view to display thumbnails. Users can tap a thumbnail to navigate to the corresponding full-sized image.

const thumbnailContentRenderItem = useCallback(
  ({ item, index }) => {
    const isActive = activeIndex === index;
    return (
      <TouchableOpacity onPress={() => scrollToActiveIndex(index)}>
        <Image
          source={{ uri: item }}
          style={[styles.thumbnailImage(IMAGE_SIZE, SPACING, isActive)]}
        />
      </TouchableOpacity>
    );
  },
  [activeIndex]
);
Enter fullscreen mode Exit fullscreen mode

🔄 Syncing Thumbnails with Fullscreen View

To ensure smooth scrolling between thumbnails and full images, we handle the onMomentumScrollEnd event and calculate the active index dynamically.

const onMomentumScrollEnd = useCallback(
  event => {
    const newIndex = Math.round(event.nativeEvent.contentOffset.x / width);
    scrollToActiveIndex(newIndex);
  },
  [width]
);
Enter fullscreen mode Exit fullscreen mode

The scrollToActiveIndex function keeps both lists in sync:

const scrollToActiveIndex = index => {
  setActiveIndex(index);
  topRef.current?.scrollToOffset?.({ offset: index * width, animated: true });

  const thumbnailOffset = index * (IMAGE_SIZE + SPACING) - width / 2 + IMAGE_SIZE / 2;
  thumbRef.current?.scrollToOffset?.({ offset: Math.max(thumbnailOffset, 0), animated: true });
};
Enter fullscreen mode Exit fullscreen mode

🏗️ Rendering the Component

Finally, we integrate both FlatList components into our main view:

return (
  <View style={styles.container}>
    <FlatList
      ref={topRef}
      data={metaData}
      renderItem={fullScreenRenderItem}
      keyExtractor={keyExtractor}
      horizontal
      showsHorizontalScrollIndicator={false}
      pagingEnabled
      onMomentumScrollEnd={onMomentumScrollEnd}
    />
    <FlatList
      ref={thumbRef}
      data={metaData}
      renderItem={thumbnailContentRenderItem}
      keyExtractor={keyExtractor}
      horizontal
      contentContainerStyle={styles.thumbnailContainer}
      showsHorizontalScrollIndicator={false}
      style={styles.thumbnailList(IMAGE_SIZE)}
    />
  </View>
);
Enter fullscreen mode Exit fullscreen mode

🎨 Styling

To make everything look sleek, we define the styles:

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#000',
  },
  fullScreenImage: {
    ...StyleSheet.absoluteFillObject,
    resizeMode: 'cover',
  },
  thumbnailImage: (IMAGE_SIZE, SPACING, isActive) => ({
    width: IMAGE_SIZE,
    height: IMAGE_SIZE,
    borderRadius: 12,
    marginRight: SPACING,
    borderWidth: 2,
    resizeMode: 'cover',
    borderColor: isActive ? '#fff' : 'transparent',
  }),
  thumbnailContainer: {
    paddingHorizontal: 10,
  },
  thumbnailList: IMAGE_SIZE => ({
    position: 'absolute',
    bottom: IMAGE_SIZE,
  }),
});
Enter fullscreen mode Exit fullscreen mode

🎯 Conclusion

With this implementation, we have created an interactive and optimized image gallery 📷 with smooth transitions and thumbnail navigation. 🎉

This gallery is highly performant thanks to useCallback, useRef, and proper scroll synchronization. ⚡ Try customizing it further by adding animations or gestures for an even better user experience! 🚀

Happy coding! 💻🔥

Comments 0 total

    Add comment