javascriptandroidiosreact-nativereact-animated

How to make infinite animation in react native?


My cards here work from right to left with a nice animation. But when the animation rewinds, the card positions rewind and there is no infinite animation image. What I want is for the first card to enter again as the last card leaves the screen and infinity occurs. how do i do this? As you can see in the gif, the animation ends and I want the last element to exit from the left of the screen and the first element to enter again.

enter image description here

import React, { useEffect, useRef } from 'react';
import { Animated, StyleSheet, Text, View, Dimensions, Image } from 'react-native';

import anonim_icon from "../img/anonim.png";

const cardData = [
    { id: 0, title: "Feeling Happy" },
    { id: 1, title: "Feeling Calm" },
    { id: 2, title: "Feeling Angry" },
    { id: 3, title: "Feeling Sad" },
];

const App = () => {
    const translateX = useRef(new Animated.Value(width)).current;

    const Cards = ({ item, index }) => {
        return (
            <View style={styles.cards_con} key={index}>
                <Image
                    style={styles.cards_ico}
                    source={anonim_icon}
                    resizeMode="contain"
                />
                <Text style={styles.card_title}>{item.title}</Text>
            </View>
        );
    };

    useEffect(() => {
        const startAnimation = () => {
            translateX.setValue(width); // Animasyon başlangıç pozisyonu (sağ dışı)
            Animated.loop(
                Animated.sequence([
                    Animated.timing(translateX, {
                        toValue: -width * cardData.length, // Sol dışına çıkış
                        duration: 17000, // Hareket süresi
                        useNativeDriver: true,
                    }),
                ]),
            ).start();
        };

        startAnimation();
    }, [translateX, width]);

    return (
        <View style={styles.container}>
            <Animated.View
                style={{
                    flexDirection: 'row',
                    transform: [{ translateX }],
                }}
            >
                <View style={{ width: width * 3 }}/>
                {cardData.map((item, index) => (
                    <Cards key={index} item={item} index={index} />
                ))}
            </Animated.View>
        </View>
    );
};

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#000",
    },
    cards_con: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "space-around",
        backgroundColor: "#323232",
        height: height / 13,
        borderRadius: 10,
        marginHorizontal: 10,
        padding: 10,
        width: width / 1.7,
    },
    cards_ico: {
        width: 30,
        height: 30,
        borderRadius: 10,
    },
    card_title: {
        color: "#FFFFFF",
        marginLeft: 10,
        fontSize: 16,
    },
});

export default App;

Solution

  • here's the modified code.

    Changes I had to do to achieve your effect:

    1. Moves the cards to the left
    2. Instantly resets position to start
    3. Restarts the animation
    4. Added proper cleanup with stopAnimation() in the useEffect cleanup function
    5. Added a clipContainer with overflow: 'hidden' to prevent visible jumping
    6. Adjusted the card width calculation to account for margins
    7. Used two sets of cards (original and duplicate) for a seamless transition
    8. Added unique keys for both sets of cards to prevent React warnings

    Make sure you add your image/icon back as I did not have the file, I removed the Icon/Image code and CSS for debugging.

    import React, { useEffect, useRef } from 'react';
    import { Animated, StyleSheet, Text, View, Dimensions } from 'react-native';
    
    const cardData = [
      { id: 0, title: "Feeling Happy" },
      { id: 1, title: "Feeling Calm" },
      { id: 2, title: "Feeling Angry" },
      { id: 3, title: "Feeling Sad" },
    ];
    
    const App = () => {
      const translateX = useRef(new Animated.Value(0)).current;
      const { width } = Dimensions.get('window');
    
      const Cards = ({ item }) => {
        return (
          <View style={styles.cards_con}>
            <Text style={styles.card_title}>{item.title}</Text>
          </View>
        );
      };
    
      useEffect(() => {
        const cardWidth = 220; // Width of card + horizontal margin
        const contentWidth = cardWidth * cardData.length;
    
        const startAnimation = () => {
          translateX.setValue(0); // Reset to start position
          
          Animated.sequence([
            Animated.timing(translateX, {
              toValue: -contentWidth,
              duration: 17000,
              useNativeDriver: true,
            }),
            // Reset the position instantly
            Animated.timing(translateX, {
              toValue: 0,
              duration: 0,
              useNativeDriver: true,
            })
          ]).start(() => {
            // Restart the animation when complete
            startAnimation();
          });
        };
    
        startAnimation();
    
        return () => {
          // Cleanup animation when component unmounts
          translateX.stopAnimation();
        };
      }, []);
    
      return (
        <View style={styles.container}>
          <View style={styles.clipContainer}>
            <Animated.View
              style={{
                flexDirection: 'row',
                transform: [{ translateX }],
              }}
            >
              {/* Original set of cards */}
              {cardData.map((item, index) => (
                <Cards key={`original-${index}`} item={item} />
              ))}
              {/* Duplicate set for seamless transition */}
              {cardData.map((item, index) => (
                <Cards key={`duplicate-${index}`} item={item} />
              ))}
            </Animated.View>
          </View>
        </View>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center",
        backgroundColor: "#000",
      },
      clipContainer: {
        overflow: 'hidden',
        width: '100%',
      },
      cards_con: {
        flexDirection: "row",
        alignItems: "center",
        justifyContent: "center",
        backgroundColor: "#323232",
        height: 50,
        borderRadius: 10,
        marginHorizontal: 10,
        padding: 10,
        width: 200,
      },
      card_title: {
        color: "#FFFFFF",
        fontSize: 16,
      },
    });
    
    export default App;