dohooo / react-native-reanimated-carousel

🎠 React Native swiper/carousel component, fully implemented using reanimated v2, support to iOS/Android/Web. (Swiper/Carousel)
https://react-native-reanimated-carousel.vercel.app
MIT License
2.83k stars 328 forks source link

Vertical scrollable content inside vertical carousel #643

Open vinz-mehra opened 3 months ago

vinz-mehra commented 3 months ago

I am trying to achieve a functionality where I put some vertical scrollable text inside a vertical carousel (react-native-reanimated-carousel). It should scroll the text first and when it reaches the end of the text then it should scroll the carousel to the next page in the same motion.

I have tried many methods but every time I had to lift my finger and again tap and scroll to trigger the carousel

Is there a way I could achieve all of this in a single motion without any janks?

const StackCards = ({ data }: { data: Post[] }) => {
const headerHeight = StatusBar.currentHeight ?? 0;
const window = Dimensions.get("window");
const PAGE_WIDTH = window.width;
const PAGE_HEIGHT = window.height - headerHeight;
const [isVertical, setIsVertical] = React.useState(true);
const [isCarEnabled, setIsCarEnabled] = React.useState(false);
const carRef = React.useRef<ICarouselInstance>(null);

const handleScrollEnd = (nativeEvent: NativeScrollEvent) => {
const contentOffsetY = nativeEvent.contentOffset.y;
const contentHeight = nativeEvent.contentSize.height;
const layoutHeight = nativeEvent.layoutMeasurement.height;
console.log(contentOffsetY);
if (contentOffsetY + layoutHeight >= contentHeight) {
  // Scrolled to the bottom, trigger next carousel item
  console.log("end");
  setIsCarEnabled(true);
} else if (contentOffsetY <= 0) {
  console.log("start");
  setIsCarEnabled(true);
} else {
}
};

return (
<View style={{ height: PAGE_HEIGHT - 50 }}>
  <Carousel
    ref={carRef}
    enabled={isCarEnabled}
    onScrollEnd={(i) => {
      setIsCarEnabled(false);
    }}
    loop
    style={{
      width: PAGE_WIDTH,
      height: PAGE_HEIGHT - 50,
      justifyContent: "center",
      alignItems: "center",
    }}
    vertical={isVertical}
    width={PAGE_WIDTH}
    height={PAGE_HEIGHT - 50}
    data={[...data]}
    renderItem={({ index, animationValue }) => (
      <Item
        key={index}
        post={data[index]}
        animationValue={animationValue}
        onScroll={handleScrollEnd}
      />
    )}
    customAnimation={animationStyle}
  />
</View>
);
};
dosubot[bot] commented 3 months ago

Hey @vinz-mehra! I'm here to help you out with any bugs, questions, or contributions you might need. Let's solve this together!

To achieve seamless scrolling behavior in a single motion where vertical scrollable text inside a vertical carousel scrolls first, and when it reaches the end, the carousel scrolls to the next page without requiring a second tap, you can use the onScrollEnd event to detect when the inner scrollable text has reached the end and then programmatically trigger the carousel to scroll to the next page.

Here's an example of how you can modify your code to achieve this behavior:

import * as React from "react";
import { View, ScrollView, Text, Dimensions, StatusBar } from "react-native";
import Carousel, { ICarouselInstance } from "react-native-reanimated-carousel";

const StackCards = ({ data }) => {
  const headerHeight = StatusBar.currentHeight ?? 0;
  const window = Dimensions.get("window");
  const PAGE_WIDTH = window.width;
  const PAGE_HEIGHT = window.height - headerHeight;
  const carRef = React.useRef<ICarouselInstance>(null);

  const handleScrollEnd = (nativeEvent) => {
    const contentOffsetY = nativeEvent.contentOffset.y;
    const contentHeight = nativeEvent.contentSize.height;
    const layoutHeight = nativeEvent.layoutMeasurement.height;

    if (contentOffsetY + layoutHeight >= contentHeight) {
      // Scrolled to the bottom, trigger next carousel item
      carRef.current?.scrollTo({
        count: 1,
        animated: true,
      });
    }
  };

  return (
    <View style={{ height: PAGE_HEIGHT - 50 }}>
      <Carousel
        ref={carRef}
        loop
        style={{
          width: PAGE_WIDTH,
          height: PAGE_HEIGHT - 50,
          justifyContent: "center",
          alignItems: "center",
        }}
        vertical
        width={PAGE_WIDTH}
        height={PAGE_HEIGHT - 50}
        data={data}
        renderItem={({ index }) => (
          <ScrollView
            onScrollEndDrag={(event) => handleScrollEnd(event.nativeEvent)}
            style={{ height: PAGE_HEIGHT - 50 }}
          >
            <Text style={{ color: "black", padding: 20 }}>
              {`Scrollable content for item ${index}`}
            </Text>
          </ScrollView>
        )}
      />
    </View>
  );
};

export default StackCards;

In this example, the handleScrollEnd function is used to detect when the inner ScrollView has reached the end of its content. When this happens, the carousel is programmatically scrolled to the next page using the scrollTo method [1][2].

To continue talking to Dosu, mention @dosu.

vinz-mehra commented 3 months ago

This does not solve it. When I reach the end of the content, the carousel gets triggered irrespective of my scroll.