dohooo/react-native-reanimated-carousel

Performance Lag in Carousel Animations on Real Android Devices Compared to Simulator

defild9 opened this issue · 3 comments

Describe the bug
On a real Android device, animations in the CarouselListItem component are noticeably slower compared to the Android simulator. This issue manifests as lag during image scrolling, item transitions, and gesture handling. The performance difference is more pronounced on real devices.

To Reproduce
Steps to reproduce the behavior:

  1. Implement the following CarouselListItem component:

    const CarouselListItem = ({
      item,
      shareHandler,
      addToWishlistHandler,
      removeFromWishlistHandler,
      stared,
    }: CarouselItem) => {
      const [currentImageIndex, setCurrentImageIndex] = React.useState(0);
    
      const baseOptions = {
        vertical: false,
        width: PAGE_WIDTH * 0.9,
        height: 232,
      } as const;
    
      const images = item?.lot?.image_urls || [];
      const isSold = item.lot.is_sold;
    
      return images.length > 0 ? (
        <CarouselContainer>
          <Carousel
            {...baseOptions}
            data={images}
            windowSize={3}
            scrollAnimationDuration={200}
            onSnapToItem={(index) => setCurrentImageIndex(index)}
            pagingEnabled={true}
            onProgressChange={(_, absoluteProgress) => {
              setCurrentImageIndex(Math.round(absoluteProgress));
            }}
            panGestureHandlerProps={{
              activeOffsetX: [-10, 10],
            }}
            renderItem={({ item }) => (
              <Flexbox>
                <ImageBackground
                  style={{ flex: 1 }}
                  imageStyle={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
                  source={{ uri: item }}
                >
                  <Flexbox justifyContent={'space-between'} borderRadius={16} height={232}>
                    <Flexbox flexDirection={'column'}>
                      <TouchableOpacity
                        onPress={shareHandler}
                        style={{
                          padding: 15,
                          backgroundColor: '#0C0C0C80',
                          borderTopLeftRadius: 16,
                          borderBottomRightRadius: 8,
                        }}
                      >
                        <ShareIcon />
                      </TouchableOpacity>
                    </Flexbox>
                    <Flexbox flexDirection={'column'}>
                      <TouchableOpacity
                        disabled={isSold}
                        onPress={
                          stared
                            ? removeFromWishlistHandler
                            : addToWishlistHandler
                            ? addToWishlistHandler
                            : () => {}
                        }
                        style={{
                          padding: 15,
                          backgroundColor: '#0C0C0C80',
                          borderTopRightRadius: 16,
                          borderBottomLeftRadius: 8,
                        }}
                      >
                        {stared ? (
                          <StarFilledIcon color={'#fff'} />
                        ) : (
                          <StarIcon color={isSold ? '#a0a0a0' : '#fff'} />
                        )}
                      </TouchableOpacity>
                    </Flexbox>
                  </Flexbox>
                </ImageBackground>
              </Flexbox>
            )}
          />
    
          <Dots
            passiveDotHeight={8}
            passiveDotWidth={8}
            activeDotWidth={12}
            activeDotHeight={8}
            length={images.length}
            active={currentImageIndex}
            activeColor="#1B6BC8"
            passiveColor="#E8EBEF"
          />
        </CarouselContainer>
      ) : (
        <TouchableWithoutFeedback>
          <Flexbox>
            <ImageBackground
              style={{ flex: 1 }}
              imageStyle={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
              source={{ uri: item.lot.image_urls[0] ?? item.lot.thumb }}
            >
              <Flexbox justifyContent={'space-between'} borderRadius={16} height={232}>
                <Flexbox flexDirection={'column'}>
                  <TouchableOpacity
                    onPress={shareHandler}
                    style={{
                      padding: 15,
                      backgroundColor: '#0C0C0C80',
                      borderTopLeftRadius: 16,
                      borderBottomRightRadius: 8,
                    }}
                  >
                    <ShareIcon />
                  </TouchableOpacity>
                </Flexbox>
                <Flexbox flexDirection={'column'}>
                  <TouchableOpacity
                    disabled={isSold}
                    onPress={
                      stared
                        ? removeFromWishlistHandler
                        : addToWishlistHandler
                        ? addToWishlistHandler
                        : () => {}
                    }
                    style={{
                      padding: 15,
                      backgroundColor: '#0C0C0C80',
                      borderTopRightRadius: 16,
                      borderBottomLeftRadius: 8,
                    }}
                  >
                    {stared ? (
                      <StarFilledIcon color={'#fff'} />
                    ) : (
                      <StarIcon color={isSold ? '#a0a0a0' : '#fff'} />
                    )}
                  </TouchableOpacity>
                </Flexbox>
              </Flexbox>
            </ImageBackground>
          </Flexbox>
        </TouchableWithoutFeedback>
      );
    };
    
    export default CarouselListItem;
  2. Run the app on both a real Android device and an Android simulator.

  3. Scroll through the images in the carousel on both platforms.

  4. Observe the difference in animation performance.

Expected behavior
Animations should be smooth and responsive on both real Android devices and Android simulators.

Screenshots
N/A

Versions (please complete the following information):

  • react: v18.2.0
  • react-native: v0.72.410
  • react-native-reanimated: v3.14.0
  • react-native-reanimated-carousel: v3.5.1
  • react-native-gesture-handler: v2.17.1

Smartphone (please complete the following information):

  • Device: Samsung Galaxy S9
  • OS: Android 10
  • Browser: N/A
  • Version: N/A

Additional context
The issue might be related to the processing power differences between simulators and real devices, or it could be due to the configuration of scrollAnimationDuration, panGestureHandlerProps, or the complexity of the components within the carousel. Testing on multiple devices and optimizing gesture handling and animations may help resolve this.

Hey there, @defild9! I'm here to help you with any bugs, questions, or contributions you have in mind. Let's squash those bugs together! 🤖

To address the performance lag in the CarouselListItem component's animations on real Android devices, you can try the following steps:

  1. Update the react-native-reanimated-carousel Package:
    Ensure you are using the latest version of the react-native-reanimated-carousel package. The latest patch reduces the amount of work done when rendering data, which should help improve performance [1].

  2. Optimize Animation Style:
    Use the interpolate and Extrapolate functions from react-native-reanimated to ensure smoother transitions and less computational overhead. Here is an example of how you can define a custom animation style:

    import { Extrapolate, interpolate } from "react-native-reanimated";
    import Carousel, { TAnimationStyle } from "react-native-reanimated-carousel";
    
    const animationStyle: TAnimationStyle = (value: number) => {
      "worklet";
    
      const itemOffsetInput = new Array(sideItemCount * 2 + 1)
        .fill(null)
        .map((_, index) => index - sideItemCount);
    
      const itemOffset = interpolate(
        value,
        itemOffsetInput,
        itemOffsetInput.map((item) => {
          if (item < 0) {
            return (-itemSize + sideItemWidth) * Math.abs(item);
          } else if (item > 0) {
            return (itemSize - sideItemWidth) * (Math.abs(item) - 1);
          }
          return 0;
        }) as number[],
      );
    
      const translate = interpolate(value, [-1, 0, 1], [-itemSize, 0, itemSize]) + centerOffset - itemOffset;
    
      const width = interpolate(value, [-1, 0, 1], [sideItemWidth, itemSize, sideItemWidth], Extrapolate.CLAMP);
    
      return {
        transform: [
          {
            translateX: translate,
          },
        ],
        width,
        overflow: "hidden",
      };
    };
  3. Adjust windowSize and scrollAnimationDuration Props:
    The windowSize prop can be used to control how many items are rendered around the current item. Reducing this number can improve performance. The default value is 0, which means all items will respond to pan gesture events [2]. You can try setting it to a smaller value, such as 3, as you have done in your example.

    The scrollAnimationDuration prop controls the duration of the scroll animation. The default value is 500 ms [2]. You can experiment with different values to find the optimal duration for smooth animations.

  4. Review Similar Issues:
    There are similar issues reported in the repository that might provide additional insights:

By following these steps, you should be able to mitigate the performance issues you're experiencing on real Android devices.

To continue talking to Dosu, mention @dosu.