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.86k stars 329 forks source link

The card in the back shows when the card data length is less than 5. #670

Closed sohuichoi closed 2 months ago

sohuichoi commented 2 months ago

Describe the bug [00:00] The card length is 5. It shows well according to the order. [00:07] The card length is 4. The card in the back shows when it's not its order. it mask the front card. This occurs when the card's length is less than 5.

To Reproduce Steps to reproduce the behavior:

Expected behavior However many length the cards have, I want the card shows according to the order. Screenshots If applicable, add screenshots to help explain your problem.

https://github.com/user-attachments/assets/ccdd0209-74d2-4621-aaa5-a406b6847d87

Versions (please complete the following information): "react": "18.2.0", "react-native": "0.73.4", "react-native-reanimated": "^3.15.1", "react-native-reanimated-carousel": "4.0.0-canary.13", "react-native-gesture-handler": "^2.16.2",

Smartphone (please complete the following information):

Additional context Add any other context about the problem here.

dosubot[bot] commented 2 months ago

To ensure that cards with a length of less than 5 display in the correct order without masking the front card, you can use the autoFillData property in the react-native-reanimated-carousel component. This property helps to fill the data to ensure proper display and avoid masking issues.

Here's how you can set it up in your Carousel component:

import * as React from "react";
import { View } from "react-native";
import Animated, {
  interpolate,
  interpolateColor,
  runOnJS,
  useAnimatedReaction,
  useAnimatedStyle,
  useSharedValue,
} from "react-native-reanimated";
import Carousel, { TAnimationStyle } from "react-native-reanimated-carousel";

import { Arrow, ArrowDirection } from "./Arrow";

import { window } from "../../constants";

function Index() {
  const headerHeight = 100;
  const PAGE_WIDTH = window.width;
  const PAGE_HEIGHT = window.height - headerHeight;
  const directionAnim = useSharedValue<ArrowDirection>(
    ArrowDirection.IS_VERTICAL,
  );
  const [isVertical, setIsVertical] = React.useState(true);

  const animationStyle: TAnimationStyle = React.useCallback(
    (value: number) => {
      "worklet";
      const translateY = interpolate(
        value,
        [-1, 0, 1],
        [-PAGE_HEIGHT, 0, 0],
      );

      const translateX = interpolate(
        value,
        [-1, 0, 1],
        [-PAGE_WIDTH, 0, 0],
      );

      const zIndex = interpolate(value, [-1, 0, 1], [300, 0, -300]);

      const scale = interpolate(value, [-1, 0, 1], [1, 1, 0.85]);

      return {
        transform: [
          isVertical ? { translateY } : { translateX },
          { scale },
        ],
        zIndex,
      };
    },
    [PAGE_HEIGHT, PAGE_WIDTH, isVertical],
  );

  useAnimatedReaction(
    () => directionAnim.value,
    (direction) => {
      switch (direction) {
        case ArrowDirection.IS_VERTICAL:
          runOnJS(setIsVertical)(true);
          break;
        case ArrowDirection.IS_HORIZONTAL:
          runOnJS(setIsVertical)(false);
          break;
      }
    },
    [],
  );

  return (
    <View style={{ flex: 1 }}>
      <Carousel
        loop
        autoFillData={true} // Ensure autoFillData is set to true
        style={{
          width: PAGE_WIDTH,
          height: PAGE_HEIGHT,
          justifyContent: "center",
          alignItems: "center",
          backgroundColor: "black",
        }}
        vertical={isVertical}
        width={PAGE_WIDTH}
        height={PAGE_HEIGHT}
        data={[...new Array(6).keys()]}
        renderItem={({ index, animationValue }) => (
          <Item
            key={index}
            index={index}
            animationValue={animationValue}
            directionAnim={directionAnim}
          />
        )}
        customAnimation={animationStyle}
      />
    </View>
  );
}

const Item: React.FC<{
  index: number
  animationValue: Animated.SharedValue<number>
  directionAnim: Animated.SharedValue<ArrowDirection>
}> = ({ animationValue, directionAnim }) => {
  const maskStyle = useAnimatedStyle(() => {
    const zIndex = interpolate(
      animationValue.value,
      [-1, 0, 1],
      [300, 0, -300],
    );

    const backgroundColor = interpolateColor(
      animationValue.value,
      [-1, 0, 1],
      ["transparent", "transparent", "rgba(0,0,0,0.3)"],
    );

    return {
      backgroundColor,
      zIndex,
    };
  }, [animationValue]);

  return (
    <View
      style={{
        backgroundColor: "white",
        flex: 1,
        borderRadius: 20,
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <Animated.View
        style={[
          maskStyle,
          { position: "absolute", width: "100%", height: "100%" },
        ]}
      />
      <Arrow directionAnim={directionAnim} />
    </View>
  );
};

export default Index;

By setting autoFillData to true, the carousel will automatically fill the data to ensure that cards with a length of less than 5 display correctly without masking the front card [1][2].

To continue talking to Dosu, mention @dosu.

satvicmovement commented 1 month ago

I faced the same issue with stacks, scrollAnimationDuration={100} solved the issue