Skip to content

Commit 1cee8ad

Browse files
authored
fix(android): prevent image jump when lifting finger during pinch zoom (#139)
1 parent b7bbb6c commit 1cee8ad

2 files changed

Lines changed: 33 additions & 7 deletions

File tree

.changeset/quick-moose-open.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"react-native-gesture-image-viewer": patch
3+
---
4+
5+
fix(android): prevent image jump when lifting finger during pinch zoom
6+
7+
Filter out sudden focal point changes when a finger is lifted during pinch-to-zoom gesture.
8+
This prevents the image from snapping to the remaining finger's position.
9+
10+
- Add lastFocalX/lastFocalY shared values to track focal point
11+
- Filter focal point changes exceeding 50px threshold
12+
- Maintain smooth zoom behavior while preventing jump artifacts
13+
14+
Related issue: [#134](https://github.com/saseungmin/react-native-gesture-image-viewer/issues/134)

src/useGestureViewer.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ export const useGestureViewer = <ItemT, LC>({
8282
const triggerTranslateY = useSharedValue(0);
8383
const triggerOpacity = useSharedValue(1);
8484

85+
const lastFocalX = useSharedValue(0);
86+
const lastFocalY = useSharedValue(0);
87+
8588
const dataLength = data?.length || 0;
8689

8790
const animationConfig = useMemo(
@@ -456,29 +459,36 @@ export const useGestureViewer = <ItemT, LC>({
456459
scheduleOnRN(setIsPinching, true);
457460
}
458461
})
459-
.onBegin(() => {
462+
.onBegin((event) => {
460463
startScale.value = scale.value;
461464
initialTranslateX.value = translateX.value;
462465
initialTranslateY.value = translateY.value;
466+
lastFocalX.value = event.focalX;
467+
lastFocalY.value = event.focalY;
463468
})
464469
.onUpdate((event) => {
465470
const newScale = startScale.value * event.scale;
466471

467472
scale.value = newScale;
468473

469-
if (event.numberOfPointers !== 2) {
470-
return;
471-
}
472-
473474
if (newScale <= 1) {
474475
translateX.value = withTiming(0);
475476
translateY.value = withTiming(0);
476477
return;
477478
}
478479

480+
const focalDeltaX = Math.abs(event.focalX - lastFocalX.value);
481+
const focalDeltaY = Math.abs(event.focalY - lastFocalY.value);
482+
const threshold = 50;
483+
484+
if (focalDeltaX < threshold && focalDeltaY < threshold) {
485+
lastFocalX.value = event.focalX;
486+
lastFocalY.value = event.focalY;
487+
}
488+
479489
const deltaScale = newScale - startScale.value;
480-
const centerX = event.focalX - width / 2;
481-
const centerY = event.focalY - height / 2;
490+
const centerX = lastFocalX.value - width / 2;
491+
const centerY = lastFocalY.value - height / 2;
482492

483493
// NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
484494
const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
@@ -551,6 +561,8 @@ export const useGestureViewer = <ItemT, LC>({
551561
width,
552562
height,
553563
constrainTranslation,
564+
lastFocalX,
565+
lastFocalY,
554566
]);
555567

556568
const zoomPanGesture = useMemo(() => {

0 commit comments

Comments
 (0)