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! ππ₯
π 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',
},
});
πΉ 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,
},
});
πΉ 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)