如何在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
相关产品推荐
相关产品推荐

