DEV Community

Cover image for πŸ“Έ Stunning Image Preview in React Native – Swipe & Rotate! πŸš€
Amit Kumar
Amit Kumar

Posted on

πŸ“Έ Stunning Image Preview in React Native – Swipe & Rotate! πŸš€

In today's digital world, images play a crucial role in enhancing user experience. Imagine an elegant image carousel where users can not only swipe through images but also preview and rotate them seamlessly! πŸŽ‰

In this guide, we’ll build an interactive image viewer in React Native with the ability to open images in full-screen preview, swipe through them horizontally, and even rotate them! πŸ”„πŸ”₯

Image description

πŸš€ Features at a Glance

βœ… Horizontal scrolling gallery
βœ… Full-screen image preview
βœ… Smooth swipe navigation
βœ… Image rotation support

Let’s dive in and bring this magic to life! ✨


πŸ“Œ Setting Up the Image Gallery

First, let’s create our main ImageView component, which displays a list of images in a horizontal scrollable FlatList. When a user taps an image, it opens in full-screen preview. πŸ–ΌοΈ

πŸ“œ ImageView.js

import {
  FlatList,
  Image,
  Modal,
  StyleSheet,
  TouchableOpacity,
  useWindowDimensions,
  View,
} from 'react-native';
import React, {useCallback, useState, useRef} from 'react';
import {CloseIcon, RotateIcon} from '../../assets';

const ImagePreview = ({flatListRef, data, selectedIndex, closePreview}) => {
  const {width, height} = useWindowDimensions();
  const listRef = useRef(null);
  const [currentIndex, setCurrentIndex] = useState(selectedIndex || 0);
  const [rotationMap, setRotationMap] = useState({});

  const rotateImage = () => {
    setRotationMap(prev => ({
      ...prev,
      [currentIndex]: prev[currentIndex] === 90 ? 0 : 90,
    }));
  };

  const renderItem = useCallback(
    ({item, index}) => {
      const rotation = rotationMap[index] || 0;
      return (
        <View key={index} style={styles.imageContainer(width, height)}>
          <Image
            source={{uri: item}}
            style={styles.image(width, height, rotation)}
          />
        </View>
      );
    },
    [rotationMap],
  );

  const onMomentumScrollEnd = useCallback(
    event => {
      const newIndex = Math.round(event.nativeEvent.contentOffset.x / width);
      setCurrentIndex(newIndex);
    },
    [width],
  );

  const getItemLayout = useCallback(
    (_, index) => ({
      length: width,
      offset: width * index,
      index,
    }),
    [width],
  );

  return (
    <Modal visible={selectedIndex !== null} transparent animationType="fade">
      <View style={styles.modalContainer}>
        <TouchableOpacity style={styles.closeButton} onPress={closePreview}>
          <CloseIcon />
        </TouchableOpacity>
        <TouchableOpacity style={styles.rotateButton} onPress={rotateImage}>
          <RotateIcon />
        </TouchableOpacity>

        <FlatList
          ref={listRef}
          data={data}
          horizontal
          pagingEnabled
          scrollEnabled={data.length > 0}
          keyExtractor={(_, index) => index.toString()}
          initialScrollIndex={selectedIndex}
          getItemLayout={getItemLayout}
          onMomentumScrollEnd={onMomentumScrollEnd}
          showsHorizontalScrollIndicator={false}
          renderItem={renderItem}
        />
      </View>
    </Modal>
  );
};

export default ImagePreview;

const styles = StyleSheet.create({
  imageContainer: (width, height) => ({
    width: width - 40,
    height,
    justifyContent: 'center',
    alignItems: 'center',
    marginHorizontal: 20,
  }),
  image: (width, height, rotation) => ({
    width: rotation % 180 === 0 ? width - 40 : height - 340,
    height: rotation % 180 === 0 ? height - 340 : width - 40,
    resizeMode: rotation % 180 === 0 ? 'cover' : 'contain',
    transform: [{rotate: `${rotation}deg`}],
    borderRadius: 14,
  }),
  closeButton: {
    position: 'absolute',
    top: 70,
    right: 20,
    zIndex: 10,
  },
  rotateButton: {
    position: 'absolute',
    top: 70,
    left: 20,
    zIndex: 10,
  },
  modalContainer: {
    flex: 1,
    backgroundColor: '#000000D0',
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Enter fullscreen mode Exit fullscreen mode

πŸ”Ή Here, FlatList is used to create a horizontal scrollable list of images.
πŸ”Ή TouchableOpacity allows tapping on images to trigger the preview.
πŸ”Ή useCallback & useMemo optimize rendering performance.


🎭 Creating the Full-Screen Image Preview

Now, let's create the ImagePreview component, which will show the selected image in full-screen mode with the ability to rotate! πŸ”„

πŸ“œ ImagePreview.js

import {
  FlatList,
  Image,
  StyleSheet,
  TouchableOpacity,
  View,
} from 'react-native';
import React, { useCallback, useState, useRef, useMemo } from 'react';
import { metaData } from '../../screens/CarouselBackgroundAnimation/data';
import ImagePreview from './ImagePreview';

const ImageView = () => {
  const [selectedIndex, setSelectedIndex] = useState(null);
  const flatListRef = useRef(null);

  const handlePreview = (index = null) => setSelectedIndex(index);

  const renderItem = useCallback(
    ({ item, index }) => (
      <TouchableOpacity
        style={styles.imageContainer}
        onPress={() => handlePreview(index)}
        activeOpacity={0.8}
      >
        <Image source={{ uri: item }} style={styles.imageStyle} />
      </TouchableOpacity>
    ),
    []
  );

  const keyExtractor = useCallback((_, index) => index.toString(), []);

  const memoizedFlatList = useMemo(
    () => (
      <FlatList
        horizontal
        data={metaData}
        renderItem={renderItem}
        keyExtractor={keyExtractor}
        contentContainerStyle={styles.contentContainerStyle}
        showsHorizontalScrollIndicator={false}
      />
    ),
    [renderItem]
  );

  return (
    <View style={styles.container}>
      {memoizedFlatList}
      {selectedIndex !== null && (
        <ImagePreview
          data={metaData}
          flatListRef={flatListRef}
          selectedIndex={selectedIndex}
          closePreview={() => handlePreview(null)}
        />
      )}
    </View>
  );
};

export default ImageView;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  imageContainer: {
    alignSelf: 'center',
    borderRadius: 14,
    overflow: 'hidden',
  },
  imageStyle: {
    width: 250,
    height: 400,
    borderRadius: 14,
  },
  contentContainerStyle: {
    gap: 24,
    paddingHorizontal: 24,
  },
});

Enter fullscreen mode Exit fullscreen mode

πŸ”Ή The FlatList inside the Modal allows horizontal swiping.
πŸ”Ή TouchableOpacity buttons let users close the modal or rotate the image.
πŸ”Ή Rotation state ensures each image remembers its rotation! πŸŒ€


πŸŽ‰ Wrapping Up!

We just built a fully interactive image viewer that allows users to preview, swipe, and rotate images seamlessly! 🎊 Whether you’re building an e-commerce app, a gallery, or a storytelling app, this feature will add wow factor to your user experience! πŸ’―

πŸ”Ή Next Step? Try adding pinch-to-zoom for an even richer experience! πŸ˜‰

πŸ”₯ What do you think? Would you add this to your project? Let’s discuss in the comments! πŸš€

Top comments (0)