You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native中如何实现卡片的无缝无限横向滚动动画?

React Native中如何实现卡片的无缝无限横向滚动动画?

从你描述的动图效果能看到,现在的卡片动画是从右到左滚动,但结束后会直接跳回初始位置,没法实现无缝的无限循环——也就是希望最后一张卡片从左侧滚出屏幕时,第一张卡片立刻从右侧进来,一直重复这个过程对吧?别担心,咱们只需要做两个关键调整就能搞定这个问题!

核心思路

要实现无缝无限滚动,关键是让用户察觉不到动画的“重置”动作:

  • 首先在滚动容器里放置两组完全相同的卡片,这样当第一组卡片滚出屏幕时,第二组刚好接上,视觉上不会有断层;
  • 然后调整动画逻辑:当第一组卡片完全滚出后,瞬间把动画的初始位置重置,同时重新启动动画——因为两组卡片内容完全一致,用户根本察觉不到这个重置操作,看起来就像是卡片在无限循环滚动。

修改后的完整代码

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;
    // 计算单张卡片的总宽度(卡片本身宽度 + 左右margin)
    const singleCardTotalWidth = width / 1.7 + 20; // marginHorizontal是10,左右各10

    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 startInfiniteAnimation = () => {
            translateX.setValue(width); // 重置初始位置到屏幕右侧外
            Animated.timing(translateX, {
                // 滚动距离为一组卡片的总宽度,确保第一组完全滚出屏幕
                toValue: -singleCardTotalWidth * cardData.length,
                duration: 17000,
                useNativeDriver: true,
            }).start(() => {
                // 动画结束后立刻重新启动,实现无缝循环
                startInfiniteAnimation();
            });
        };

        startInfiniteAnimation();
    }, [translateX, width, singleCardTotalWidth]);

    return (
        <View style={styles.container}>
            <Animated.View
                style={{
                    flexDirection: 'row',
                    transform: [{ translateX }],
                }}
            >
                {/* 渲染两组相同的卡片,实现无缝衔接 */}
                {[...cardData, ...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;

关键修改点说明

  1. 复制卡片数组:用[...cardData, ...cardData]渲染两组卡片,这样第一组滚完后第二组刚好跟上,避免出现空白;
  2. 修正滚动距离计算:原来直接用width * cardData.length不准确,我们计算了单张卡片的实际总宽度(卡片宽度+左右margin),确保第一组卡片能完全滚出屏幕;
  3. 替换循环逻辑:去掉Animated.loop,改用动画结束后递归调用启动函数的方式,这样在动画结束时瞬间重置位置并重新启动——因为两组卡片内容一致,视觉上完全无缝。

备注:内容来源于stack exchange,提问作者selcukctn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 12:53:07