Skip to content

Instantly share code, notes, and snippets.

@tchayen
Created September 20, 2020 16:14
Show Gist options
  • Save tchayen/b3475a6238453028285ed1631343548c to your computer and use it in GitHub Desktop.
Save tchayen/b3475a6238453028285ed1631343548c to your computer and use it in GitHub Desktop.
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
Copy link

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