react-native-gesture-image-viewer 1.2.3 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -13
- package/lib/module/GestureViewer.js +3 -2
- package/lib/module/GestureViewer.js.map +1 -1
- package/lib/module/GestureViewerManager.js +127 -6
- package/lib/module/GestureViewerManager.js.map +1 -1
- package/lib/module/useGestureViewer.js +70 -39
- package/lib/module/useGestureViewer.js.map +1 -1
- package/lib/module/useGestureViewerController.js +4 -0
- package/lib/module/useGestureViewerController.js.map +1 -1
- package/lib/module/utils.js +23 -0
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/src/GestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/GestureViewerManager.d.ts +42 -0
- package/lib/typescript/src/GestureViewerManager.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewer.d.ts +9 -0
- package/lib/typescript/src/useGestureViewer.d.ts.map +1 -1
- package/lib/typescript/src/useGestureViewerController.d.ts +4 -0
- package/lib/typescript/src/useGestureViewerController.d.ts.map +1 -1
- package/lib/typescript/src/utils.d.ts +11 -0
- package/lib/typescript/src/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/GestureViewer.tsx +20 -12
- package/src/GestureViewerManager.ts +152 -5
- package/src/useGestureViewer.ts +79 -44
- package/src/useGestureViewerController.ts +4 -0
- package/src/utils.ts +21 -0
package/src/useGestureViewer.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import type GestureViewerManager from './GestureViewerManager';
|
|
20
20
|
import { registry } from './GestureViewerRegistry';
|
|
21
21
|
import type { GestureViewerProps } from './types';
|
|
22
|
+
import { createBoundsConstraint } from './utils';
|
|
22
23
|
|
|
23
24
|
type UseGestureViewerProps<T = any> = Omit<
|
|
24
25
|
GestureViewerProps<T>,
|
|
@@ -50,7 +51,7 @@ export const useGestureViewer = <T = any>({
|
|
|
50
51
|
const width = useSnap ? customWidth || screenWidth : screenWidth;
|
|
51
52
|
|
|
52
53
|
const [isZoomed, setIsZoomed] = useState(false);
|
|
53
|
-
|
|
54
|
+
const [isRotated, setIsRotated] = useState(false);
|
|
54
55
|
const [currentIndex, setCurrentIndex] = useState(initialIndex);
|
|
55
56
|
const [manager, setManager] = useState<GestureViewerManager | null>(null);
|
|
56
57
|
|
|
@@ -64,29 +65,14 @@ export const useGestureViewer = <T = any>({
|
|
|
64
65
|
const translateX = useSharedValue(0);
|
|
65
66
|
const scale = useSharedValue(1);
|
|
66
67
|
const backdropOpacity = useSharedValue(1);
|
|
68
|
+
const rotation = useSharedValue(0);
|
|
67
69
|
|
|
68
70
|
const listRef = useRef<any>(null);
|
|
69
71
|
|
|
70
72
|
const dataLength = data?.length || 0;
|
|
71
73
|
|
|
72
|
-
const
|
|
73
|
-
(
|
|
74
|
-
'worklet';
|
|
75
|
-
if (scale <= 1) {
|
|
76
|
-
return {
|
|
77
|
-
x: translateX,
|
|
78
|
-
y: translateY,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const maxTranslateX = (width * scale - width) / 2;
|
|
83
|
-
const maxTranslateY = (screenHeight * scale - screenHeight) / 2;
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
x: Math.max(-maxTranslateX, Math.min(maxTranslateX, translateX)),
|
|
87
|
-
y: Math.max(-maxTranslateY, Math.min(maxTranslateY, translateY)),
|
|
88
|
-
};
|
|
89
|
-
},
|
|
74
|
+
const constrainTranslation = useMemo(
|
|
75
|
+
() => createBoundsConstraint({ width, height: screenHeight }),
|
|
90
76
|
[width, screenHeight],
|
|
91
77
|
);
|
|
92
78
|
|
|
@@ -97,6 +83,13 @@ export const useGestureViewer = <T = any>({
|
|
|
97
83
|
},
|
|
98
84
|
);
|
|
99
85
|
|
|
86
|
+
useAnimatedReaction(
|
|
87
|
+
() => rotation.value,
|
|
88
|
+
(currentRotation) => {
|
|
89
|
+
runOnJS(setIsRotated)(currentRotation % 360 !== 0);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
|
|
100
93
|
useEffect(() => {
|
|
101
94
|
const handleManagerChange = (manager: GestureViewerManager | null) => {
|
|
102
95
|
unsubscribeRef.current?.();
|
|
@@ -132,8 +125,24 @@ export const useGestureViewer = <T = any>({
|
|
|
132
125
|
manager.setEnableSwipeGesture(enableSwipeGesture);
|
|
133
126
|
manager.setCurrentIndex(initialIndex);
|
|
134
127
|
manager.setWidth(width + itemSpacing);
|
|
128
|
+
manager.setHeight(screenHeight);
|
|
129
|
+
manager.setZoomSharedValues(scale, translateX, translateY, maxZoomScale);
|
|
130
|
+
manager.setRotation(rotation);
|
|
135
131
|
manager.notifyStateChange();
|
|
136
|
-
}, [
|
|
132
|
+
}, [
|
|
133
|
+
dataLength,
|
|
134
|
+
enableSwipeGesture,
|
|
135
|
+
initialIndex,
|
|
136
|
+
manager,
|
|
137
|
+
width,
|
|
138
|
+
itemSpacing,
|
|
139
|
+
maxZoomScale,
|
|
140
|
+
scale,
|
|
141
|
+
screenHeight,
|
|
142
|
+
translateX,
|
|
143
|
+
translateY,
|
|
144
|
+
rotation,
|
|
145
|
+
]);
|
|
137
146
|
|
|
138
147
|
useEffect(() => {
|
|
139
148
|
if (!manager || !listRef.current) {
|
|
@@ -153,6 +162,7 @@ export const useGestureViewer = <T = any>({
|
|
|
153
162
|
scale.value = 1;
|
|
154
163
|
backdropOpacity.value = 1;
|
|
155
164
|
startScale.value = 1;
|
|
165
|
+
rotation.value = 0;
|
|
156
166
|
|
|
157
167
|
if (initialIndex <= 0 || !listRef.current) {
|
|
158
168
|
return;
|
|
@@ -175,7 +185,7 @@ export const useGestureViewer = <T = any>({
|
|
|
175
185
|
return () => {
|
|
176
186
|
runAfterInteractions?.cancel();
|
|
177
187
|
};
|
|
178
|
-
}, [initialIndex, translateY, backdropOpacity, translateX, scale, startScale, width, itemSpacing]);
|
|
188
|
+
}, [initialIndex, translateY, backdropOpacity, translateX, scale, startScale, width, itemSpacing, rotation]);
|
|
179
189
|
|
|
180
190
|
const onMomentumScrollEnd = useCallback(
|
|
181
191
|
(event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
|
@@ -199,6 +209,7 @@ export const useGestureViewer = <T = any>({
|
|
|
199
209
|
initialTranslateY.value = withTiming(0);
|
|
200
210
|
startScale.value = withTiming(1);
|
|
201
211
|
scale.value = withTiming(1);
|
|
212
|
+
rotation.value = 0;
|
|
202
213
|
}
|
|
203
214
|
},
|
|
204
215
|
[
|
|
@@ -214,6 +225,7 @@ export const useGestureViewer = <T = any>({
|
|
|
214
225
|
initialTranslateX,
|
|
215
226
|
initialTranslateY,
|
|
216
227
|
startScale,
|
|
228
|
+
rotation,
|
|
217
229
|
],
|
|
218
230
|
);
|
|
219
231
|
|
|
@@ -252,25 +264,30 @@ export const useGestureViewer = <T = any>({
|
|
|
252
264
|
.onUpdate((event) => {
|
|
253
265
|
const newScale = startScale.value * event.scale;
|
|
254
266
|
|
|
267
|
+
scale.value = newScale;
|
|
268
|
+
|
|
269
|
+
if (newScale <= 1) {
|
|
270
|
+
translateX.value = withTiming(0);
|
|
271
|
+
translateY.value = withTiming(0);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
255
275
|
const deltaScale = newScale - startScale.value;
|
|
256
276
|
const centerX = event.focalX - width / 2;
|
|
257
277
|
const centerY = event.focalY - screenHeight / 2;
|
|
258
278
|
|
|
259
|
-
scale.value = newScale;
|
|
260
279
|
// NOTE 새로운 이동값 = 기존 이동값 - (중심점 거리 × 스케일 변화량) / 원래 스케일 (중심점이 화면 중심에서 멀수록, 확대 배율이 클수록 더 많이 이동)
|
|
261
280
|
const newTranslateX = initialTranslateX.value - (centerX * deltaScale) / startScale.value;
|
|
262
281
|
const newTranslateY = initialTranslateY.value - (centerY * deltaScale) / startScale.value;
|
|
263
282
|
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
283
|
+
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
|
|
284
|
+
translateX: newTranslateX,
|
|
285
|
+
translateY: newTranslateY,
|
|
286
|
+
scale: newScale,
|
|
287
|
+
});
|
|
271
288
|
|
|
272
|
-
translateX.value =
|
|
273
|
-
translateY.value =
|
|
289
|
+
translateX.value = constrainedTranslateX;
|
|
290
|
+
translateY.value = constrainedTranslateY;
|
|
274
291
|
})
|
|
275
292
|
.onEnd(() => {
|
|
276
293
|
if (scale.value > maxZoomScale) {
|
|
@@ -279,10 +296,14 @@ export const useGestureViewer = <T = any>({
|
|
|
279
296
|
easing: Easing.bezier(0.25, 0.1, 0.25, 1.0),
|
|
280
297
|
});
|
|
281
298
|
|
|
282
|
-
const
|
|
299
|
+
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
|
|
300
|
+
translateX: translateX.value,
|
|
301
|
+
translateY: translateY.value,
|
|
302
|
+
scale: maxZoomScale,
|
|
303
|
+
});
|
|
283
304
|
|
|
284
|
-
translateX.value = withTiming(
|
|
285
|
-
translateY.value = withTiming(
|
|
305
|
+
translateX.value = withTiming(constrainedTranslateX);
|
|
306
|
+
translateY.value = withTiming(constrainedTranslateY);
|
|
286
307
|
|
|
287
308
|
return;
|
|
288
309
|
}
|
|
@@ -299,9 +320,14 @@ export const useGestureViewer = <T = any>({
|
|
|
299
320
|
return;
|
|
300
321
|
}
|
|
301
322
|
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
323
|
+
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
|
|
324
|
+
translateX: translateX.value,
|
|
325
|
+
translateY: translateY.value,
|
|
326
|
+
scale: scale.value,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
translateX.value = withTiming(constrainedTranslateX);
|
|
330
|
+
translateY.value = withTiming(constrainedTranslateY);
|
|
305
331
|
});
|
|
306
332
|
}, [
|
|
307
333
|
scale,
|
|
@@ -314,7 +340,7 @@ export const useGestureViewer = <T = any>({
|
|
|
314
340
|
initialTranslateY,
|
|
315
341
|
width,
|
|
316
342
|
screenHeight,
|
|
317
|
-
|
|
343
|
+
constrainTranslation,
|
|
318
344
|
]);
|
|
319
345
|
|
|
320
346
|
const zoomPanGesture = useMemo(() => {
|
|
@@ -331,10 +357,14 @@ export const useGestureViewer = <T = any>({
|
|
|
331
357
|
const newTranslateX = initialTranslateX.value + event.translationX;
|
|
332
358
|
const newTranslateY = initialTranslateY.value + event.translationY;
|
|
333
359
|
|
|
334
|
-
const
|
|
360
|
+
const { translateX: constrainedTranslateX, translateY: constrainedTranslateY } = constrainTranslation({
|
|
361
|
+
translateX: newTranslateX,
|
|
362
|
+
translateY: newTranslateY,
|
|
363
|
+
scale: scale.value,
|
|
364
|
+
});
|
|
335
365
|
|
|
336
|
-
translateX.value =
|
|
337
|
-
translateY.value =
|
|
366
|
+
translateX.value = constrainedTranslateX;
|
|
367
|
+
translateY.value = constrainedTranslateY;
|
|
338
368
|
}
|
|
339
369
|
});
|
|
340
370
|
}, [
|
|
@@ -345,7 +375,7 @@ export const useGestureViewer = <T = any>({
|
|
|
345
375
|
scale,
|
|
346
376
|
initialTranslateX,
|
|
347
377
|
initialTranslateY,
|
|
348
|
-
|
|
378
|
+
constrainTranslation,
|
|
349
379
|
]);
|
|
350
380
|
|
|
351
381
|
const doubleTapGesture = useMemo(() => {
|
|
@@ -392,7 +422,12 @@ export const useGestureViewer = <T = any>({
|
|
|
392
422
|
|
|
393
423
|
const animatedStyle = useAnimatedStyle(() => {
|
|
394
424
|
return {
|
|
395
|
-
transform: [
|
|
425
|
+
transform: [
|
|
426
|
+
{ translateY: translateY.value },
|
|
427
|
+
{ translateX: translateX.value },
|
|
428
|
+
{ scale: scale.value },
|
|
429
|
+
{ rotate: `${rotation.value}deg` },
|
|
430
|
+
],
|
|
396
431
|
};
|
|
397
432
|
});
|
|
398
433
|
|
|
@@ -412,7 +447,7 @@ export const useGestureViewer = <T = any>({
|
|
|
412
447
|
translateY,
|
|
413
448
|
listRef,
|
|
414
449
|
isZoomed,
|
|
415
|
-
|
|
450
|
+
isRotated,
|
|
416
451
|
dismissGesture,
|
|
417
452
|
zoomGesture,
|
|
418
453
|
|
|
@@ -42,6 +42,10 @@ export const useGestureViewerController = (id = 'default') => {
|
|
|
42
42
|
goToIndex: manager?.goToIndex || noopFunction,
|
|
43
43
|
goToPrevious: manager?.goToPrevious || noopFunction,
|
|
44
44
|
goToNext: manager?.goToNext || noopFunction,
|
|
45
|
+
zoomIn: manager?.zoomIn || noopFunction,
|
|
46
|
+
zoomOut: manager?.zoomOut || noopFunction,
|
|
47
|
+
resetZoom: manager?.resetZoom || noopFunction,
|
|
48
|
+
rotate: manager?.rotate || noopFunction,
|
|
45
49
|
currentIndex: state.currentIndex,
|
|
46
50
|
totalCount: state.dataLength,
|
|
47
51
|
};
|
package/src/utils.ts
CHANGED
|
@@ -27,3 +27,24 @@ export const isFlashListLike = (component: any): component is any => {
|
|
|
27
27
|
|
|
28
28
|
return component?.name === 'FlashList';
|
|
29
29
|
};
|
|
30
|
+
|
|
31
|
+
export const createBoundsConstraint =
|
|
32
|
+
({ width, height }: { width: number; height: number }) =>
|
|
33
|
+
({ scale, translateX, translateY }: { translateX: number; translateY: number; scale: number }) => {
|
|
34
|
+
'worklet';
|
|
35
|
+
|
|
36
|
+
if (scale <= 1) {
|
|
37
|
+
return {
|
|
38
|
+
translateX,
|
|
39
|
+
translateY,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const maxTranslateX = (width * scale - width) / 2;
|
|
44
|
+
const maxTranslateY = (height * scale - height) / 2;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
translateX: Math.max(-maxTranslateX, Math.min(maxTranslateX, translateX)),
|
|
48
|
+
translateY: Math.max(-maxTranslateY, Math.min(maxTranslateY, translateY)),
|
|
49
|
+
};
|
|
50
|
+
};
|