Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save tanner-west/9ca24e3487d53964f4774b8f8d4142b1 to your computer and use it in GitHub Desktop.
Save tanner-west/9ca24e3487d53964f4774b8f8d4142b1 to your computer and use it in GitHub Desktop.
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