-
-
Notifications
You must be signed in to change notification settings - Fork 332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gesture swipe on images with desktop? #685
Comments
To enable swipe left/right functionality with the mouse on an image using
Here is the modified code: import * as React from "react";
import type { ICarouselInstance } from "react-native-reanimated-carousel";
import Carousel from "react-native-reanimated-carousel";
import { SafeAreaView } from "react-native-safe-area-context";
import { SBItem } from "../../components/SBItem";
import { window } from "../../constants";
import { Button, Image, ImageSourcePropType, ViewStyle, useWindowDimensions } from "react-native";
import Animated, { Easing, Extrapolate, FadeIn, interpolate, runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, withDecay, withSpring, withTiming } from "react-native-reanimated";
import { Gesture, GestureDetector } from "react-native-gesture-handler";
import * as Haptics from 'expo-haptics';
import { getImages } from "./images";
const PAGE_WIDTH = window.width;
const data = getImages().slice(0, 68);
function Index() {
const windowWidth = useWindowDimensions().width;
const scrollOffsetValue = useSharedValue<number>(0);
const ref = React.useRef<ICarouselInstance>(null);
const baseOptions = {
vertical: false,
width: windowWidth,
height: PAGE_WIDTH / 2,
} as const
const gesture = React.useMemo(() => Gesture
.Pan()
.onUpdate((event) => {
if (event.translationX > 0) {
ref.current?.scrollTo({ index: Math.max(0, ref.current?.getCurrentIndex() - 1), animated: true });
} else {
ref.current?.scrollTo({ index: Math.min(data.length - 1, ref.current?.getCurrentIndex() + 1), animated: true });
}
}),
[ref]
);
return (
<SafeAreaView
edges={["bottom"]}
style={{
flex: 1,
backgroundColor: 'black',
justifyContent: "center",
}}
>
<GestureDetector gesture={gesture}>
<Carousel
{...baseOptions}
loop={false}
enabled={false} // Disable default pan gesture
ref={ref}
defaultScrollOffsetValue={scrollOffsetValue}
testID={"xxx"}
style={{ width: "100%" }}
autoPlay={false}
autoPlayInterval={1000}
data={data}
onConfigurePanGesture={g => g.enabled(false)}
pagingEnabled
onSnapToItem={index => console.log("current index:", index)}
windowSize={2}
renderItem={({ index, item }) => {
return <Animated.View
key={index}
style={{ flex: 1 }}
>
<SBItem
showIndex={false}
img={item}
/>
</Animated.View>
}}
/>
</GestureDetector>
<ThumbnailPagination
style={{ marginVertical: 9 }}
onIndexChange={(index) => {
ref.current?.scrollTo({ index, animated: false })
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)
}}
/>
</SafeAreaView>
);
}
const ThumbnailPagination: React.FC<{
style?: ViewStyle
onIndexChange?: (index: number) => void
}> = ({ style, onIndexChange }) => {
const [_containerWidth, setContainerWidth] = React.useState<number>(0);
const inactiveWidth = 30;
const activeWidth = inactiveWidth * 2;
const itemGap = 5;
const totalWidth = inactiveWidth * (data.length - 1) + activeWidth + itemGap * (data.length - 1);
const swipeProgress = useSharedValue<number>(0);
const activeIndex = useSharedValue<number>(0);
const containerWidth = React.useMemo(() => {
if (totalWidth < _containerWidth) {
return totalWidth
}
return _containerWidth
}, [
_containerWidth,
totalWidth,
])
const gesture = React.useMemo(() => Gesture
.Pan()
.onUpdate((event) => {
swipeProgress.value = Math.min(
Math.max(event.x, 0),
containerWidth
)
}),
[
activeWidth,
inactiveWidth,
containerWidth,
]
)
const animStyles = useAnimatedStyle(() => {
if (containerWidth <= 0) {
return {}
}
const isOverScroll = totalWidth > containerWidth
if (!isOverScroll) {
return {
transform: [
{
translateX: 0
}
],
}
}
return {
transform: [
{
translateX: -interpolate(
swipeProgress.value,
[0, containerWidth],
[0, totalWidth - containerWidth],
Extrapolate.CLAMP
)
}
],
}
}, [
containerWidth,
totalWidth,
containerWidth
])
useAnimatedReaction(
() => activeIndex.value,
(activeIndex) => onIndexChange && runOnJS(onIndexChange)(activeIndex),
[onIndexChange]
)
return <GestureDetector gesture={gesture}>
<Animated.View style={{ width: '100%', overflow: "hidden" }}>
<Animated.View style={[{ flexDirection: "row" }, style, animStyles]} onLayout={e => setContainerWidth(e.nativeEvent.layout.width)}>
{
containerWidth > 0 && data.map((item, index) => {
return <ThumbnailPaginationItem
key={index}
source={item}
totalItems={data.length}
swipeProgress={swipeProgress}
containerWidth={containerWidth}
activeIndex={activeIndex}
activeWidth={activeWidth}
itemGap={itemGap}
inactiveWidth={inactiveWidth}
totalWidth={totalWidth}
index={index}
style={{ marginRight: itemGap }}
onSwipe={() => {
console.log(`${item} swiped`)
}}
/>
})
}
</Animated.View>
</Animated.View>
</GestureDetector >
}
const ThumbnailPaginationItem: React.FC<{
source: ImageSourcePropType;
containerWidth: number;
totalItems: number;
activeIndex: Animated.SharedValue<number>;
swipeProgress: Animated.SharedValue<number>;
activeWidth: number;
totalWidth: number;
inactiveWidth: number;
itemGap: number;
index: number;
onSwipe?: () => void;
style?: ViewStyle
}> = ({
source,
containerWidth,
totalItems,
swipeProgress,
index,
itemGap = 0,
activeIndex,
activeWidth,
totalWidth,
inactiveWidth,
style
}) => {
const isActive = useSharedValue(0);
useAnimatedReaction(
() => {
const onTheRight = index >= activeIndex.value
const extraWidth = onTheRight ? activeWidth - inactiveWidth : 0
const inputRange = [
index * (inactiveWidth + itemGap) + (index === activeIndex.value ? 0 : extraWidth) - 0.1,
index * (inactiveWidth + itemGap) + (index === activeIndex.value ? 0 : extraWidth),
(index + 1) * (inactiveWidth + itemGap) + extraWidth,
(index + 1) * (inactiveWidth + itemGap) + extraWidth + 0.1,
]
return interpolate(
swipeProgress.value / containerWidth * totalWidth,
inputRange,
[
0,
1,
1,
0
],
Extrapolate.CLAMP
)
},
(_isActiveAnimVal) => {
isActive.value = _isActiveAnimVal
},
[
containerWidth,
totalItems,
index,
activeIndex,
activeWidth,
inactiveWidth,
itemGap,
]
)
useAnimatedReaction(
() => {
return isActive.value
},
(isActiveVal) => {
if (isActiveVal === 1) {
activeIndex.value = index
}
},
[]
)
const animStyles = useAnimatedStyle(() => {
const widthAnimVal = interpolate(
isActive.value,
[
0, 1, 1, 0
],
[
inactiveWidth,
activeWidth,
activeWidth,
inactiveWidth
],
Extrapolate.CLAMP
)
return {
width: withTiming(widthAnimVal, { duration: 100, easing: Easing.bounce }),
height: 30,
borderRadius: 5,
overflow: "hidden",
}
}, [
isActive,
activeWidth,
inactiveWidth,
])
return <Animated.View style={[animStyles, style]}>
<Image source={source} style={{ width: '100%', height: "100%" }} />
</Animated.View>
}
export default Index; This code sets up a custom pan gesture to handle mouse swipes and scrolls the carousel accordingly [1][2]. |
I'm looking at your examples, especially the normal one here: https://reanimated-carousel.dev/Examples/normal
Repro:
When you do a long press on the item and it changes to an image, how can we get it to swipe left/right when using the mouse on an image?
It works on mobile but desktop, it tries to drag the image itself.
The text was updated successfully, but these errors were encountered: