From 29b75ec3d3a994c9cb0c5a4ab150e5ecf5bb25cc Mon Sep 17 00:00:00 2001 From: locphamtran Date: Thu, 10 Sep 2026 15:59:15 +0700 Subject: [PATCH] Zoom the finished photo with a double tap only --- src/components/ZoomableImage.tsx | 68 +++++++++----------------------- 1 file changed, 18 insertions(+), 50 deletions(-) diff --git a/src/components/ZoomableImage.tsx b/src/components/ZoomableImage.tsx index 0b613c8..4f40c6b 100644 --- a/src/components/ZoomableImage.tsx +++ b/src/components/ZoomableImage.tsx @@ -1,25 +1,21 @@ import React, { useRef } from 'react'; import { Animated, PanResponder } from 'react-native'; -// Pinch / double-tap zoom for one preview photo inside the paging strip. +// Double-tap zoom for one preview photo inside the paging strip. // Core PanResponder + Animated only (no react-native-gesture-handler dep). // While zoomed in, responder termination is refused so the parent ScrollView // cannot steal the gesture — the photo pans instead of paging; swiping to the -// next photo resumes after zooming back out (double tap or pinch below 1.15). -// Two-finger pinch only zooms once the photo is already zoomed in: at 100% the -// pinch stays inert, so the paging strip still behaves like a plain gallery. +// next photo resumes after zooming back out (double tap). // -// ponytail: pinch zooms around the image center, not the finger midpoint; -// focal zoom needs per-touch location math against the image's screen rect. -// MAX_SCALE is a hard cap — add a dedicated slider/stepper when 4x is not -// enough. +// ponytail: double tap only — a finished photo is not a map, and the two-finger +// gesture conflicted with paging. Re-add a pinch branch in onPanResponderMove +// (scale = start * dist/startDist, clamped) if pinch is ever wanted back. interface ZoomableImageProps { uri: string; width: number; } -const MAX_SCALE = 4; const TAP_SCALE = 2.5; const TAP_MS = 300; @@ -34,7 +30,6 @@ export default function ZoomableImage({ uri, width }: ZoomableImageProps) { ty: 0, w: width, h: 0, - pinchStart: null as { scale: number; dist: number } | null, lastDx: 0, lastDy: 0, moved: false, @@ -71,9 +66,7 @@ export default function ZoomableImage({ uri, width }: ZoomableImageProps) { }; const finish = () => { - const wasTap = - !st.moved && Date.now() - st.grantAt < 400 && st.pinchStart === null; - st.pinchStart = null; + const wasTap = !st.moved && Date.now() - st.grantAt < 400; st.lastDx = 0; st.lastDy = 0; st.moved = false; @@ -99,52 +92,27 @@ export default function ZoomableImage({ uri, width }: ZoomableImageProps) { PanResponder.create({ onStartShouldSetPanResponder: () => true, onMoveShouldSetPanResponder: () => true, - // Page the strip while zoom is off; once zoomed (or mid-pinch) the - // image keeps the gesture for panning/zooming itself. - onPanResponderTerminationRequest: () => - st.scale <= 1.001 && st.pinchStart === null, + // Page the strip while zoom is off; once zoomed the image keeps the + // gesture so a drag pans instead of paging. + onPanResponderTerminationRequest: () => st.scale <= 1.001, onPanResponderGrant: () => { st.grantAt = Date.now(); st.moved = false; st.lastDx = 0; st.lastDy = 0; }, - onPanResponderMove: (e, g) => { - const touches = e.nativeEvent.touches; - if (touches.length >= 2) { - const a = touches[0]; - const b = touches[1]; - const dist = Math.hypot(a.pageX - b.pageX, a.pageY - b.pageY); - if (!st.pinchStart) st.pinchStart = { scale: st.scale, dist: dist || 1 }; - const next = - st.pinchStart.scale > 1.001 - ? Math.max( - 1, - Math.min(MAX_SCALE, st.pinchStart.scale * (dist / st.pinchStart.dist)) - ) - : 1; - st.scale = next; - if (next <= 1.001) { - st.tx = 0; - st.ty = 0; - } + onPanResponderMove: (_e, g) => { + // Single finger only: pan the zoomed photo. A second finger is ignored + // on purpose (double tap is the only way in and out of zoom). + if (Math.abs(g.dx) > 8 || Math.abs(g.dy) > 8) st.moved = true; + if (st.scale > 1.001) { + st.tx += g.dx - st.lastDx; + st.ty += g.dy - st.lastDy; clampTx(); apply(); - st.moved = true; - st.lastDx = g.dx; - st.lastDy = g.dy; - } else { - st.pinchStart = null; - if (Math.abs(g.dx) > 8 || Math.abs(g.dy) > 8) st.moved = true; - if (st.scale > 1.001) { - st.tx += g.dx - st.lastDx; - st.ty += g.dy - st.lastDy; - clampTx(); - apply(); - } - st.lastDx = g.dx; - st.lastDy = g.dy; } + st.lastDx = g.dx; + st.lastDy = g.dy; }, onPanResponderRelease: finish, onPanResponderTerminate: finish,