fix zoom image

This commit is contained in:
m
2026-07-16 09:27:43 +02:00
parent 2dac600d06
commit 04e74316f9
2 changed files with 131 additions and 42 deletions
+76 -20
View File
@@ -1,5 +1,5 @@
import React, { useCallback } from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, Pressable, View } from 'react-native';
import Animated, {
useSharedValue,
useAnimatedStyle,
@@ -16,14 +16,14 @@ interface ZoomableImageProps {
uri: string;
width: number;
height: number;
onScaleChange?: (scale: number) => void;
onClose?: () => void;
}
const MAX_SCALE = 4;
const DOUBLE_TAP_SCALE = 2.5;
const SPRING_CONFIG = { damping: 20, stiffness: 200, mass: 0.5 };
export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableImageProps) {
export function ZoomableImage({ uri, width, height, onClose }: ZoomableImageProps) {
const scale = useSharedValue(1);
const savedScale = useSharedValue(1);
const translateX = useSharedValue(0);
@@ -31,14 +31,13 @@ export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableIma
const savedTranslateX = useSharedValue(0);
const savedTranslateY = useSharedValue(0);
const notifyScale = useCallback((s: number) => {
onScaleChange?.(s);
}, [onScaleChange]);
const handleClose = useCallback(() => {
onClose?.();
}, [onClose]);
const pinch = Gesture.Pinch()
.onUpdate((e) => {
scale.value = Math.min(Math.max(savedScale.value * e.scale, 1), MAX_SCALE);
runOnJS(notifyScale)(scale.value);
})
.onEnd(() => {
if (scale.value < 1) {
@@ -48,15 +47,13 @@ export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableIma
savedScale.value = 1;
savedTranslateX.value = 0;
savedTranslateY.value = 0;
runOnJS(notifyScale)(1);
} else {
savedScale.value = scale.value;
}
});
const pan = Gesture.Pan()
.minDistance(10)
.activeOffsetX([-15, 15])
.minDistance(5)
.onUpdate((e) => {
if (savedScale.value > 1) {
translateX.value = savedTranslateX.value + e.translationX;
@@ -72,6 +69,8 @@ export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableIma
savedTranslateY.value = translateY.value;
});
const zoomGestures = Gesture.Simultaneous(pinch, pan);
const doubleTap = Gesture.Tap()
.numberOfTaps(2)
.maxDuration(250)
@@ -83,15 +82,23 @@ export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableIma
savedScale.value = 1;
savedTranslateX.value = 0;
savedTranslateY.value = 0;
runOnJS(notifyScale)(1);
} else {
scale.value = withTiming(DOUBLE_TAP_SCALE, { duration: 200 });
savedScale.value = DOUBLE_TAP_SCALE;
runOnJS(notifyScale)(DOUBLE_TAP_SCALE);
}
});
const composed = Gesture.Exclusive(pinch, doubleTap, pan);
const singleTap = Gesture.Tap()
.maxDuration(250)
.onEnd(() => {
if (scale.value <= 1) {
runOnJS(handleClose)();
}
});
const taps = Gesture.Exclusive(doubleTap, singleTap);
const composed = Gesture.Race(zoomGestures, taps);
const animatedStyle = useAnimatedStyle(() => ({
transform: [
@@ -102,18 +109,67 @@ export function ZoomableImage({ uri, width, height, onScaleChange }: ZoomableIma
}));
return (
<GestureDetector gesture={composed}>
<Animated.Image
source={{ uri }}
style={[styles.image, { width, height }, animatedStyle]}
resizeMode="contain"
/>
</GestureDetector>
<View style={styles.container}>
<GestureDetector gesture={composed}>
<Animated.Image
source={{ uri }}
style={[styles.image, { width, height }, animatedStyle]}
resizeMode="contain"
/>
</GestureDetector>
{onClose && (
<Pressable style={styles.closeButton} onPress={onClose}>
<View style={styles.closeIcon}>
<View style={[styles.closeLine, styles.closeLine1]} />
<View style={[styles.closeLine, styles.closeLine2]} />
</View>
</Pressable>
)}
</View>
);
}
const CLOSE_SIZE = 36;
const CLOSE_LINE = 20;
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
justifyContent: 'center',
alignItems: 'center',
},
image: {
overflow: 'hidden',
},
closeButton: {
position: 'absolute',
top: 56,
right: 16,
width: CLOSE_SIZE,
height: CLOSE_SIZE,
borderRadius: CLOSE_SIZE / 2,
backgroundColor: 'rgba(255,255,255,0.25)',
justifyContent: 'center',
alignItems: 'center',
},
closeIcon: {
width: CLOSE_LINE,
height: CLOSE_LINE,
justifyContent: 'center',
alignItems: 'center',
},
closeLine: {
position: 'absolute',
width: CLOSE_LINE,
height: 2,
backgroundColor: '#fff',
borderRadius: 1,
},
closeLine1: {
transform: [{ rotate: '45deg' }],
},
closeLine2: {
transform: [{ rotate: '-45deg' }],
},
});