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

如何在React+TailwindCSS+Framer Motion中实现带内容切换遮罩的垂直无限滚动动画

如何在React+TailwindCSS+Framer Motion中实现带内容切换遮罩的垂直无限滚动动画

嘿,这个效果我之前做过类似的,刚好可以给你一步步拆解怎么实现,结合React、TailwindCSS和Framer Motion完全能搞定!先理清楚核心需求:固定高度的遮罩(h-20)、垂直无限循环的支付卡片、卡片经过遮罩中心时自动切换文字状态,对吧?

核心思路拆解

要实现这个效果,我们需要三个关键部分:

  • 遮罩容器:用overflow-hidden的固定高度容器,只显示当前滚动到的卡片
  • 无限滚动动画:通过重复渲染两组相同的卡片,配合Framer Motion的无限循环动画,实现无缝滚动
  • 内容切换逻辑:监听卡片的位置,当它的中心和遮罩中心重合时,切换文字内容

具体代码实现

首先导入需要的依赖:

import { motion, useMotionValue } from "framer-motion";
import { useState, useEffect, useRef } from "react";

1. 主组件:遮罩+滚动容器

export default function PaymentCardScroller() {
  // 模拟支付卡片数据,你可以换成自己的业务数据
  const cardList = Array.from({ length: 3 }, (_, index) => ({ id: index + 1 }));
  // 滚动动画持续时间(秒),数值越大滚动越慢
  const scrollSpeed = 7;

  // 控制滚动容器的y轴偏移量
  const scrollYOffset = useMotionValue(0);

  // 初始化无限滚动动画
  useEffect(() => {
    const scrollAnimation = scrollYOffset.start({
      y: ["0%", "-100%"],
      duration: scrollSpeed,
      ease: "linear",
      repeat: Infinity,
      repeatType: "loop",
    });

    // 组件卸载时停止动画
    return () => scrollAnimation.stop();
  }, [scrollYOffset, scrollSpeed]);

  return (
    {/* 遮罩容器:固定高度h-20,溢出隐藏,只显示当前卡片 */}
    <div className="relative w-full h-20 overflow-hidden border rounded-lg shadow-sm">
      {/* 滚动容器:绝对定位,包含两组相同的卡片实现无缝衔接 */}
      <motion.div 
        className="absolute w-full" 
        style={{ y: scrollYOffset }}
      >
        {/* 第一组卡片 */}
        {cardList.map(card => (
          <PaymentCard key={`primary-${card.id}`} />
        ))}
        {/* 第二组卡片:和第一组完全相同,滚动时衔接第一组末尾 */}
        {cardList.map(card => (
          <PaymentCard key={`duplicate-${card.id}`} />
        ))}
      </motion.div>
    </div>
  );
}

2. 卡片组件:内容切换逻辑

function PaymentCard() {
  const [isSuccess, setIsSuccess] = useState(false);
  const cardRef = useRef(null);
  // 跟踪卡片的y轴位置
  const cardYPosition = useMotionValue(0);
  // 遮罩高度是h-20(80px),中心位置为40px
  const maskCenterY = 40;
  // 切换状态的误差范围,避免频繁切换
  const triggerThreshold = 10;

  // 监听卡片位置,切换支付状态
  useEffect(() => {
    const updatePaymentStatus = () => {
      if (!cardRef.current) return;
      
      // 计算卡片的中心位置:当前y偏移 + 卡片高度的一半
      const currentCardY = cardYPosition.get();
      const cardCenterY = currentCardY + 40;

      // 当卡片中心接近遮罩中心时,切换为成功状态,否则切回失败
      if (Math.abs(cardCenterY - maskCenterY) <= triggerThreshold) {
        setIsSuccess(true);
      } else {
        setIsSuccess(false);
      }
    };

    // 监听y值变化,实时更新状态
    const unsubscribe = cardYPosition.onChange(updatePaymentStatus);
    // 组件挂载时初始化一次状态
    updatePaymentStatus();

    return () => unsubscribe();
  }, [cardYPosition]);

  return (
    <motion.div
      ref={cardRef}
      className="h-20 flex items-center justify-center bg-gray-50 border-b last:border-b-0"
      style={{ y: cardYPosition }}
    >
      <p 
        className={`text-base font-semibold transition-colors duration-300 
          ${isSuccess ? 'text-green-600' : 'text-red-600'}`}
      >
        {isSuccess ? "Payment Successful" : "Payment Failed"}
      </p>
    </motion.div>
  );
}

关键细节说明

  • 无限滚动的实现:我们渲染了两组完全相同的卡片,当第一组滚动出遮罩时,第二组的第一个卡片刚好接上,配合repeat: Infinity的动画,就实现了无缝无限滚动的效果。
  • 内容切换时机:通过计算卡片中心和遮罩中心的距离,当两者足够接近时切换文字,加入误差范围是为了避免卡片滚动时状态频繁跳动。
  • 样式适配:所有卡片高度都设为h-20,和遮罩高度一致,确保同一时间只有一个卡片完全显示在遮罩内。

你可以根据自己的需求调整动画速度、卡片样式、触发阈值这些参数,这个方案完全能满足你描述的效果~

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 07:39:37