Last active
July 2, 2022 02:34
-
-
Save tanner-west/9ca24e3487d53964f4774b8f8d4142b1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from "react"; | |
import { | |
View, | |
SafeAreaView, | |
Image, | |
TouchableOpacity, | |
LayoutChangeEvent, | |
} from "react-native"; | |
import Animated, { | |
useSharedValue, | |
useAnimatedStyle, | |
useAnimatedScrollHandler, | |
interpolate, | |
useDerivedValue, | |
useAnimatedRef, | |
SharedValue, | |
} from "react-native-reanimated"; | |
const BLOCK_HEIGHT = 300; | |
interface BlockProps { | |
block: { color: string }; | |
scrollY: SharedValue<number>; | |
viewHeight: number; | |
scrollRef: React.RefObject<any>; | |
} | |
const Block = ({ block, scrollY, viewHeight, scrollRef }: BlockProps) => { | |
const itemY = useSharedValue(0); | |
const onBlockLayout = (e: LayoutChangeEvent) => { | |
itemY.value = e.nativeEvent.layout.y; | |
}; | |
const midpoint = viewHeight / 2; | |
const distanceFromCenter = useDerivedValue(() => { | |
return scrollY.value + midpoint - itemY.value - BLOCK_HEIGHT / 2; | |
}); | |
const animatedStyle = useAnimatedStyle(() => { | |
// If the item's distance from the center is greater than half its height, always return the half-height value, | |
// otherwise return the absolute value of the actual distance (using the absolute value means we don't care whether which side of the midpoint the item is on). | |
// We only care about the actual distance if it less than half the items height, since that tells us the item is approaching the midpoint. | |
const distance = !isNaN(distanceFromCenter.value) | |
? Math.abs(distanceFromCenter.value) > BLOCK_HEIGHT / 2 | |
? BLOCK_HEIGHT / 2 | |
: Math.abs(distanceFromCenter.value) | |
: BLOCK_HEIGHT / 2; | |
// Scale the item based on how close to the screen midpoint it is, starting and ending when it is half its height away | |
return { | |
transform: [ | |
{ | |
scale: interpolate(distance, [BLOCK_HEIGHT / 2, 0], [1, 1.2]), | |
}, | |
], | |
zIndex: distance < BLOCK_HEIGHT / 2 ? BLOCK_HEIGHT / 2 : 0, | |
}; | |
}); | |
return ( | |
<Animated.View | |
onLayout={onBlockLayout} | |
style={[ | |
{ | |
height: BLOCK_HEIGHT, | |
width: BLOCK_HEIGHT, | |
margin: 5, | |
borderColor: "white", | |
borderWidth: 5, | |
backgroundColor: block.color, | |
}, | |
animatedStyle, | |
]} | |
> | |
<TouchableOpacity | |
onPress={() => { | |
scrollRef.current.scrollTo({ | |
y: itemY.value - (midpoint - BLOCK_HEIGHT / 2), | |
}); | |
}} | |
activeOpacity={1} | |
> | |
<View style={{ height: BLOCK_HEIGHT, width: BLOCK_HEIGHT }}></View> | |
</TouchableOpacity> | |
</Animated.View> | |
); | |
}; | |
export default function InterpolationScroll() { | |
const [height, setHeight] = useState<number | undefined>(); | |
let scrollRef = useAnimatedRef<any>(); | |
const scrollY = useSharedValue(0); | |
const onScrollLayout = (e: LayoutChangeEvent) => { | |
setHeight(e.nativeEvent.layout.height); | |
}; | |
const scrollHandler = useAnimatedScrollHandler({ | |
onScroll: (e) => { | |
scrollY.value = e.contentOffset.y; | |
}, | |
}); | |
const blocks = [ | |
{ | |
color: "#AC3931", | |
}, | |
{ | |
color: "#482C3D", | |
}, | |
{ | |
color: "#E5D352", | |
}, | |
{ | |
color: "#537D8D", | |
}, | |
]; | |
return ( | |
<SafeAreaView style={{ flex: 1 }}> | |
<Animated.ScrollView | |
scrollEventThrottle={1} | |
style={{ flex: 1, backgroundColor: "#D6CBC1" }} | |
contentContainerStyle={{ alignItems: "center" }} | |
onScroll={scrollHandler} | |
snapToInterval={310} | |
snapToAlignment={"center"} | |
decelerationRate={"fast"} | |
onLayout={onScrollLayout} | |
ref={scrollRef} | |
contentOffset={{ | |
x: 0, | |
y: height | |
? Math.abs( | |
height - height / 2 - BLOCK_HEIGHT / 2 + 5 - BLOCK_HEIGHT + 10 | |
) | |
: 0, | |
}} | |
> | |
<View | |
style={[ | |
{ | |
height: BLOCK_HEIGHT, | |
width: BLOCK_HEIGHT, | |
margin: 5, | |
}, | |
]} | |
/> | |
{blocks.map((block, index) => ( | |
<Block | |
key={index} | |
scrollRef={scrollRef} | |
block={block} | |
scrollY={scrollY} | |
viewHeight={height || 0} | |
/> | |
))} | |
<View | |
style={[ | |
{ | |
height: BLOCK_HEIGHT, | |
width: BLOCK_HEIGHT, | |
margin: 5, | |
}, | |
]} | |
/> | |
</Animated.ScrollView> | |
</SafeAreaView> | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment