react-native-zoom-toolkit
Version:
Most complete set of pinch to zoom utilites for React Native
215 lines (214 loc) • 6.52 kB
JavaScript
import React, { useContext, useImperativeHandle, useState } from 'react';
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, withTiming } from 'react-native-reanimated';
import { clamp } from '../../commons/utils/clamp';
import { getMaxScale } from '../../commons/utils/getMaxScale';
import { getScrollPosition } from '../../commons/utils/getScrollPosition';
import GalleryGestureHandler from './GalleryGestureHandler';
import GalleryItem from './GalleryItem';
import { GalleryContext } from './context';
const Gallery = props => {
const {
reference,
data,
renderItem,
keyExtractor,
initialIndex = 0,
windowSize = 5,
maxScale: userMaxScale = 6,
vertical = false,
gap = 0,
allowOverflow = false,
tapOnEdgeToItem = true,
zoomEnabled = true,
scaleMode = 'bounce',
pinchMode = 'clamp',
allowPinchPanning = true,
longPressDuration = 500,
customTransition,
onIndexChange,
onScroll,
onTap,
onUpdate,
onPanStart,
onPanEnd,
onPinchStart,
onPinchEnd,
onSwipe,
onLongPress,
onZoomBegin,
onZoomEnd,
onVerticalPull,
onGestureEnd
} = props;
const {
activeIndex,
rootSize,
rootChildSize,
scroll,
translate,
scale,
hasZoomed,
overflow
} = useContext(GalleryContext);
const nextItems = Math.floor(windowSize / 2);
const [scrollIndex, setScrollIndex] = useState(initialIndex);
const itemSize = useDerivedValue(() => {
return vertical ? rootSize.height.value : rootSize.width.value;
}, [vertical, rootSize]);
const maxScale = useDerivedValue(() => {
if (typeof userMaxScale === 'object') {
if (userMaxScale.length === 0) return 6;
return getMaxScale({
width: rootChildSize.width.value,
height: rootChildSize.height.value
}, userMaxScale[activeIndex.value]);
}
return userMaxScale;
}, [userMaxScale, activeIndex, rootChildSize]);
const measureRoot = e => {
const {
width,
height
} = e.nativeEvent.layout;
const scrollPosition = getScrollPosition({
index: activeIndex.get(),
itemSize: vertical ? height : width,
gap
});
rootSize.width.set(width);
rootSize.height.set(height);
scroll.set(scrollPosition);
};
const animatedStyles = useAnimatedStyle(() => ({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
overflow: overflow.value
}), [overflow]);
useDerivedValue(() => {
onUpdate === null || onUpdate === void 0 || onUpdate({
containerSize: {
width: rootSize.width.value,
height: rootSize.height.value
},
childSize: {
width: rootChildSize.width.value,
height: rootChildSize.height.value
},
maxScale: maxScale.value,
translateX: translate.x.value,
translateY: translate.y.value,
scale: scale.value
});
}, [rootSize, rootChildSize, maxScale, translate, scale]);
useAnimatedReaction(() => ({
scroll: scroll.value,
itemSize: itemSize.value
}), value => onScroll === null || onScroll === void 0 ? void 0 : onScroll(value.scroll, (data.length - 1) * value.itemSize), [scroll, itemSize]);
useAnimatedReaction(() => activeIndex.value, value => {
onIndexChange && runOnJS(onIndexChange)(value);
runOnJS(setScrollIndex)(value);
}, [activeIndex]);
useAnimatedReaction(() => ({
vertical,
size: {
width: rootSize.width.value,
height: rootSize.height.value
}
}), value => {
scroll.value = getScrollPosition({
index: activeIndex.value,
itemSize: value.vertical ? value.size.height : value.size.width,
gap
});
}, [vertical, rootSize]);
useAnimatedReaction(() => scale.value, (value, previousValue) => {
if (value !== 1 && !hasZoomed.value) {
hasZoomed.value = true;
onZoomBegin && runOnJS(onZoomBegin)(activeIndex.value);
} else if (value === 1 && previousValue !== 1 && hasZoomed.value) {
hasZoomed.value = false;
onZoomEnd && runOnJS(onZoomEnd)(activeIndex.value);
}
}, [scale]);
// Reference handling
const setIndex = index => {
const clamped = clamp(index, 0, data.length - 1);
activeIndex.value = clamped;
scroll.value = getScrollPosition({
index: activeIndex.get(),
itemSize: itemSize.get(),
gap
});
};
const getState = () => {
return {
containerSize: {
width: rootSize.width.value,
height: rootSize.height.value
},
childSize: {
width: rootChildSize.width.value,
height: rootChildSize.height.value
},
maxScale: maxScale.value,
translateX: translate.x.value,
translateY: translate.y.value,
scale: scale.value
};
};
const reset = (animate = true) => {
translate.x.value = animate ? withTiming(0) : 0;
translate.y.value = animate ? withTiming(0) : 0;
scale.value = animate ? withTiming(1) : 1;
};
useImperativeHandle(reference, () => ({
setIndex,
reset,
getState: getState
}));
return /*#__PURE__*/React.createElement(Animated.View, {
testID: 'root',
style: animatedStyles,
onLayout: measureRoot
}, data.map((item, index) => {
const inLowerHalf = index < scrollIndex - nextItems;
const inUpperHalf = index > scrollIndex + nextItems;
if (inLowerHalf || inUpperHalf) return null;
const key = (keyExtractor === null || keyExtractor === void 0 ? void 0 : keyExtractor(item, index)) ?? `item-${index}`;
return /*#__PURE__*/React.createElement(GalleryItem, {
key: key,
zIndex: data.length - index,
index: index,
gap: gap,
item: item,
vertical: vertical,
renderItem: renderItem,
customTransition: customTransition
});
}), /*#__PURE__*/React.createElement(GalleryGestureHandler, {
gap: gap,
maxScale: maxScale,
itemSize: itemSize,
length: data.length,
vertical: vertical,
tapOnEdgeToItem: tapOnEdgeToItem,
zoomEnabled: zoomEnabled,
scaleMode: scaleMode,
allowOverflow: allowOverflow,
allowPinchPanning: allowPinchPanning,
pinchMode: pinchMode,
longPressDuration: longPressDuration,
onTap: onTap,
onPanStart: onPanStart,
onPanEnd: onPanEnd,
onPinchStart: onPinchStart,
onPinchEnd: onPinchEnd,
onSwipe: onSwipe,
onLongPress: onLongPress,
onVerticalPull: onVerticalPull,
onGestureEnd: onGestureEnd
}));
};
export default Gallery;
//# sourceMappingURL=Gallery.js.map