Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
import React from 'react';
import {View, Dimensions} from 'react-native';
import Animated, {
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
const BORDER_RADIUS = 10;
const SCREEN_WIDTH = Dimensions.get('screen').width;
const palette = [
'#FFB3B3',
'#FFDCB3',
'#FFF8B3',
'#E2FFB3',
'#B3FFDB',
'#B3DBFF',
'#B3BBFF',
'#DEB3FF',
];
const CARD_WIDTH = 260;
const IMAGE_HEIGHT = Math.floor((CARD_WIDTH / 16) * 9);
const CARD_HEIGHT = IMAGE_HEIGHT + 100;
const MARGIN = 16;
const SCALE = 0.85;
const Card = ({index, lastOne, scroll}) => {
const animated = useAnimatedStyle(() => {
let value = 1;
const current = Math.round(scroll.value);
if (index === current - 1 || index === current + 1) {
const offset = 2 * Math.abs(scroll.value - current);
value = SCALE + offset * (1 - SCALE);
}
return {
transform: [{scale: value}],
};
});
return (
<View
style={{
width: CARD_WIDTH,
height: CARD_HEIGHT,
marginRight: lastOne ? 0 : MARGIN,
}}>
<Animated.View
style={[
{
width: CARD_WIDTH,
height: CARD_HEIGHT,
borderRadius: BORDER_RADIUS,
backgroundColor: '#fff',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.1,
shadowRadius: 4,
},
animated,
]}>
<View
style={{
width: CARD_WIDTH,
height: IMAGE_HEIGHT,
backgroundColor: palette[index],
borderTopLeftRadius: BORDER_RADIUS,
borderTopRightRadius: BORDER_RADIUS,
}}
/>
<View style={{padding: 16}}></View>
</Animated.View>
</View>
);
};
const Cards = ({items}) => {
const scroll = useSharedValue(0);
const handleScroll = useAnimatedScrollHandler(({contentOffset: {x}}) => {
const current = x / (CARD_WIDTH + MARGIN);
scroll.value = current;
});
return (
<Animated.ScrollView
snapToInterval={CARD_WIDTH + MARGIN}
contentOffset={{
y: 0,
x: (CARD_WIDTH + MARGIN) * Math.floor(items.length / 2),
}}
decelerationRate="fast"
horizontal
showsHorizontalScrollIndicator={false}
onScroll={handleScroll}
scrollEventThrottle={16}
style={{
top: 0,
left: 0,
position: 'absolute',
paddingVertical: 20 + 16,
}}
contentContainerStyle={{
paddingHorizontal: (SCREEN_WIDTH - CARD_WIDTH) / 2,
}}>
{items.map((item, index) => (
<Card
index={index}
scroll={scroll}
lastOne={index === items.length - 1}
/>
))}
</Animated.ScrollView>
);
};
export default Cards;
@derekblank

This comment has been minimized.

Copy link

@derekblank derekblank commented Jul 7, 2021

Works great. If it helps anyone else, ScrollView has a property called disableIntervalMomentum that will stop scrolling on each index, regardless of how fast the gesture is. (I noticed on the video and my own testing that it is possible to flick through more than one item per gesture without disableIntervalMomentum={true}, but maybe some people want that.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment