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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 03:16:05