Skip to content

Instantly share code, notes, and snippets.

@jckw
Last active November 3, 2021 21:31
Show Gist options
  • Save jckw/b19158406ce544785d5fd354fa547af8 to your computer and use it in GitHub Desktop.
Save jckw/b19158406ce544785d5fd354fa547af8 to your computer and use it in GitHub Desktop.
Super simple Tinder-style swipeable Card component with Reanimated 2
import { Text } from "react-native"
import React from "react"
import { PanGestureHandler } from "react-native-gesture-handler"
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withSpring,
} from "react-native-reanimated"
import useDimensions from "../hooks/useDimensions"
interface IProps {
id: string
title: string
subtitle: string
body: string
onSwipe: (direction: "left" | "right" | "up") => void
}
const HORIZONTAL_THRESHOLD = 200
const VERTICAL_THRESHOLD = 255
const Card = ({ id, title, subtitle, body, onSwipe }: IProps) => {
const pressed = useSharedValue(false)
const startingPosition = 0
const x = useSharedValue(startingPosition)
const y = useSharedValue(startingPosition)
const onSwipeLeft = () => onSwipe("left")
const onSwipeRight = () => onSwipe("right")
const onSwipeUp = () => onSwipe("up")
const dimensions = useDimensions()
const eventHandler = useAnimatedGestureHandler({
onStart: (event, ctx) => {
pressed.value = true
},
onActive: (event, ctx) => {
x.value = startingPosition + event.translationX
y.value = startingPosition + event.translationY
},
onEnd: (event, ctx) => {
pressed.value = false
if (event.translationX < -HORIZONTAL_THRESHOLD) {
x.value = withSpring(
-dimensions.screen.width,
{ mass: 0.8 },
runOnJS(onSwipeLeft),
)
return
}
if (event.translationX > HORIZONTAL_THRESHOLD) {
x.value = withSpring(
dimensions.screen.width,
{ mass: 0.8 },
runOnJS(onSwipeRight),
)
return
}
if (event.translationY < -VERTICAL_THRESHOLD) {
y.value = withSpring(
-dimensions.screen.height,
{ mass: 0.8 },
runOnJS(onSwipeUp),
)
return
}
x.value = withSpring(startingPosition, { mass: 0.8 })
y.value = withSpring(startingPosition, { mass: 0.8 })
},
})
const uas = useAnimatedStyle(() => {
return {
transform: [
{ scale: withSpring(pressed.value ? 1.05 : 1, { mass: 0.1 }) },
{ translateX: x.value },
{ translateY: y.value },
{ rotate: (x.value / dimensions.screen.width) * 0.15 },
],
}
})
return (
<PanGestureHandler onGestureEvent={eventHandler}>
<Animated.View
style={[
{
position: "absolute",
backgroundColor: "#fff",
borderRadius: 16,
shadowColor: "#000",
shadowOpacity: 0.15,
shadowRadius: 20,
padding: 30,
width: 285,
},
uas,
]}
>
<Text>{subtitle}</Text>
<Text>{title}</Text>
<Text>{body}</Text>
</Animated.View>
</PanGestureHandler>
)
}
export default Card
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment