I currently have a horizontal ScrollView
with a few pink boxes in it. These boxes are built using Animated.View
and a GestureDetector
to manage dragging of the box. I want to be able to drag the pink box outside of the scroll view area (the blue box), so that it becomes "detached". Currently when I drag a box "outside" of the scroll view, it's still technically a child of the scroll view, so scrolling the scroll view cuases the pink box to be scrolled also.
I've tried to create a minimal reproducible example of my issue. Here is a GIF of my problem:

Above you can see that when I drag the pink box outside of the scroll view area, the pink box is still scrolled even when "outside" of the scroll view/blue scroll area. Additionally, the pink box can't be dragged/panned once moved to the white area outside of the scroll view which isn't desired either.
I have created this expo snack to show my issue and so you can try it for yourself. This is best tested on iOS (within the Expo Go app).
Below is my code for achieving the above example. I'm using overflow: visible
on the scroll view to allow the pink box to be visible once dragged outside of the scroll view, which I think is part of the problem:
App.js
export default function App() {
const boxes = useState([{id: 0}, {id: 1}]);
return (
<SafeAreaView>
<GestureHandlerRootView style={styles.container}>
<View style={styles.scrollViewContainer}>
<ScrollView style={styles.scrollViewStyles} horizontal={true}>
{boxes.map(({id}) => <Box key={id} />)}
</ScrollView>
</View>
<StatusBar style="auto" />
</GestureHandlerRootView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
},
scrollViewContainer: {
width: '100%',
backgroundColor: "lightblue",
height: 150,
},
scrollViewStyles: {
overflow: "visible"
}
});
And here is my Box
component that is being rendered above in App:
const SIZE = 100;
export default function Box(props) {
const translateX = useSharedValue(0);
const translateY = useSharedValue(0);
const context = useSharedValue({x: 0, y: 0});
const panGesture = Gesture.Pan()
.onBegin(() => {
context.value = {x: translateX.value, y: translateY.value};
})
.onUpdate((event) => {
translateX.value = event.translationX + context.value.x;
translateY.value = event.translationY + context.value.y;
});
const panStyle = useAnimatedStyle(() => ({
transform: [
{translateX: withSpring(translateX.value)},
{translateY: withSpring(translateY.value)},
]
}));
return (<GestureDetector gesture={panGesture}>
<Animated.View style={[styles.box, panStyle]} />
</GestureDetector>);
}
const styles = StyleSheet.create({
box: {
width: SIZE,
height: SIZE,
marginHorizontal: 10,
borderRadius: 20,
backgroundColor: "pink",
shadowColor: "#000",
shadowOffset: {width: 0, height: 0},
shadowOpacity: 0.5,
},
});