ReactJS动态队列UI动画实现求助(基于Tailwind CSS)
React队列系统Tailwind动画实现方案
核心思路
之前的transform动画仅做视觉位移,元素实际布局位置未同步更新。要解决这个问题,需要结合状态控制+Tailwind过渡/动画类,让动画过程与队列数据的更新联动:入队/出队时先通过状态标记动画阶段,利用Tailwind实现平滑过渡,动画结束后同步更新队列数据,确保布局位置与视觉效果一致。
实现步骤
1. 自定义Tailwind动画(tailwind.config.js)
先在Tailwind配置中定义滑入、滑出及元素左移的动画:
/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { extend: { keyframes: { slideInRight: { '0%': { transform: 'translateX(100%)', opacity: 0 }, '100%': { transform: 'translateX(0)', opacity: 1 } }, slideOutLeft: { '0%': { transform: 'translateX(0)', opacity: 1 }, '100%': { transform: 'translateX(-100%)', opacity: 0 } }, slideLeft: { '0%': { transform: 'translateX(0)' }, '100%': { transform: 'translateX(-calc(100% + 0.5rem))' } // 偏移量=元素宽度+间距 } }, animation: { 'slide-in-right': 'slideInRight 0.3s ease-out forwards', 'slide-out-left': 'slideOutLeft 0.3s ease-in forwards', 'slide-left': 'slideLeft 0.3s ease-out forwards' } }, }, plugins: [], }
2. React组件实现
通过维护动画状态,联动队列数据与视觉效果:
import { useState, useEffect } from 'react'; function QueueSystem() { const [queue, setQueue] = useState([]); const [animatingOutIndex, setAnimatingOutIndex] = useState(null); const [inputVal, setInputVal] = useState(''); // 入队逻辑 const enqueue = () => { if (!inputVal.trim()) return; // 先添加带"待滑入"状态的元素 const tempQueue = [...queue, { id: Date.now(), value: inputVal, isEntering: true }]; setQueue(tempQueue); setInputVal(''); // 延迟触发滑入动画 setTimeout(() => { setQueue(prev => prev.map(item => ({ ...item, isEntering: false }))); }, 10); }; // 出队逻辑 const dequeue = () => { if (queue.length === 0 || animatingOutIndex !== null) return; // 标记第一个元素进入出队动画 setAnimatingOutIndex(0); // 动画结束后移除元素,触发剩余元素左滑 setTimeout(() => { const remainingItems = queue.slice(1); // 标记剩余元素需要左滑 const slidingQueue = remainingItems.map(item => ({ ...item, isSlidingLeft: true })); setQueue(slidingQueue); // 左滑动画结束后清除状态 setTimeout(() => { setQueue(slidingQueue.map(item => ({ ...item, isSlidingLeft: false }))); setAnimatingOutIndex(null); }, 300); }, 300); }; return ( <div className="max-w-4xl mx-auto p-4"> <div className="flex gap-2 mb-4"> <input type="text" value={inputVal} onChange={(e) => setInputVal(e.target.value)} className="border p-2 flex-1" placeholder="输入队列元素" /> <button onClick={enqueue} className="bg-blue-500 text-white px-4 py-2">入队</button> <button onClick={dequeue} className="bg-red-500 text-white px-4 py-2">出队</button> </div> <div className="flex gap-2 overflow-x-auto pb-2 snap-x snap-mandatory"> {queue.map((item, idx) => ( <div key={item.id} className={` flex-shrink-0 w-24 h-24 bg-gray-100 flex items-center justify-center rounded border transition-all duration-300 snap-center ${item.isEntering ? 'translate-x-full opacity-0' : ''} ${idx === animatingOutIndex ? 'animate-slide-out-left' : ''} ${item.isSlidingLeft ? 'animate-slide-left' : ''} `} > {item.value} </div> ))} </div> </div> ); } export default QueueSystem;
3. 关键细节说明
- 入队动画:新元素先通过
translate-x-full opacity-0定位到右侧,延迟10ms后移除该类,利用Tailwind的transition-all实现滑入效果,也可直接使用自定义的animate-slide-in-right类。 - 出队动画:标记第一个元素执行
animate-slide-out-left,动画结束后移除该元素,同时给剩余元素添加animate-slide-left动画,让它们滑动到新位置,动画结束后清除状态,确保后续操作正常。 - 布局同步:动画结束后更新队列数组,让DOM结构与队列数据保持一致,彻底解决视觉与实际位置脱节的问题。
- 滚动吸附兼容:保留原有的
snap-x snap-mandatory snap-center类,动画完成后依然能正常触发滚动吸附。
优化建议
- 若队列元素宽度不固定,可通过
useRef获取元素实际宽度,动态计算slideLeft动画的偏移量,避免硬编码。 - 用
onAnimationEnd事件替代setTimeout监听动画结束,逻辑会更精准:
const handleOutAnimationEnd = () => { // 执行移除元素、触发剩余元素左滑的逻辑 };
内容的提问来源于stack exchange,提问作者Andrew Noviello
相关产品推荐
相关产品推荐

