react-native-image-viewing
Version:
React Native modal component for viewing images as a sliding gallery
80 lines (79 loc) • 4.05 kB
JavaScript
/**
* Copyright (c) JOB TODAY S.A. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import React, { useCallback, useRef, useState } from "react";
import { Animated, Dimensions, ScrollView, StyleSheet, View, TouchableWithoutFeedback, } from "react-native";
import useDoubleTapToZoom from "../../hooks/useDoubleTapToZoom";
import useImageDimensions from "../../hooks/useImageDimensions";
import { getImageStyles, getImageTransform } from "../../utils";
import { ImageLoading } from "./ImageLoading";
const SWIPE_CLOSE_OFFSET = 75;
const SWIPE_CLOSE_VELOCITY = 1.55;
const SCREEN = Dimensions.get("screen");
const SCREEN_WIDTH = SCREEN.width;
const SCREEN_HEIGHT = SCREEN.height;
const ImageItem = ({ imageSrc, onZoom, onRequestClose, onLongPress, delayLongPress, swipeToCloseEnabled = true, doubleTapToZoomEnabled = true, }) => {
const scrollViewRef = useRef(null);
const [loaded, setLoaded] = useState(false);
const [scaled, setScaled] = useState(false);
const imageDimensions = useImageDimensions(imageSrc);
const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN);
const [translate, scale] = getImageTransform(imageDimensions, SCREEN);
const scrollValueY = new Animated.Value(0);
const scaleValue = new Animated.Value(scale || 1);
const translateValue = new Animated.ValueXY(translate);
const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1;
const imageOpacity = scrollValueY.interpolate({
inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET],
outputRange: [0.5, 1, 0.5],
});
const imagesStyles = getImageStyles(imageDimensions, translateValue, scaleValue);
const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity };
const onScrollEndDrag = useCallback(({ nativeEvent }) => {
var _a, _b, _c, _d;
const velocityY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.velocity) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
const scaled = ((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1;
onZoom(scaled);
setScaled(scaled);
if (!scaled &&
swipeToCloseEnabled &&
Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY) {
onRequestClose();
}
}, [scaled]);
const onScroll = ({ nativeEvent, }) => {
var _a, _b, _c, _d;
const offsetY = (_c = (_b = (_a = nativeEvent) === null || _a === void 0 ? void 0 : _a.contentOffset) === null || _b === void 0 ? void 0 : _b.y, (_c !== null && _c !== void 0 ? _c : 0));
if (((_d = nativeEvent) === null || _d === void 0 ? void 0 : _d.zoomScale) > 1) {
return;
}
scrollValueY.setValue(offsetY);
};
const onLongPressHandler = useCallback((event) => {
onLongPress(imageSrc);
}, [imageSrc, onLongPress]);
return (<View>
<ScrollView ref={scrollViewRef} style={styles.listItem} pinchGestureEnabled showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} maximumZoomScale={maxScale} contentContainerStyle={styles.imageScrollContainer} scrollEnabled={swipeToCloseEnabled} onScrollEndDrag={onScrollEndDrag} scrollEventThrottle={1} {...(swipeToCloseEnabled && {
onScroll,
})}>
{(!loaded || !imageDimensions) && <ImageLoading />}
<TouchableWithoutFeedback onPress={doubleTapToZoomEnabled ? handleDoubleTap : undefined} onLongPress={onLongPressHandler} delayLongPress={delayLongPress}>
<Animated.Image source={imageSrc} style={imageStylesWithOpacity} onLoad={() => setLoaded(true)}/>
</TouchableWithoutFeedback>
</ScrollView>
</View>);
};
const styles = StyleSheet.create({
listItem: {
width: SCREEN_WIDTH,
height: SCREEN_HEIGHT,
},
imageScrollContainer: {
height: SCREEN_HEIGHT,
},
});
export default React.memo(ImageItem);