两个DIV实现无缝轮播

思路

其实实现思路就是设置两个变量 isEvenisAnimating 通过isEven 来控制当前展示哪个div;isAnimating来管理当前状态。

核心 Api 就是 transitionend 事件监听动画结束。

状态分为两种 进行中: next 下一个进入 active 当前展示

准备中: enter 从下方进入时的动画目标状态 exit 向上退出时的动画目标状态

代码实现

javascript 复制代码
import React, { useState, useEffect, useMemo } from 'react';
import styles from './index.module.scss';
const NewSwiper = ({ items, speed = 4000 }) => {
  // `isEven` 用于跟踪两个轮播槽位('偶数槽'或'奇数槽')中哪个是当前激活的
  const [isEven, setIsEven] = useState(true);
  // `currentIndex` 指向当前在激活槽中可见的项目索引
  const [currentIndex, setCurrentIndex] = useState(0);
  // `isAnimating` 是控制是否应用动画类的标志
  const [isAnimating, setIsAnimating] = useState(false);


  const nextIndex = useMemo(() => {
    if (!items || items.length === 0) return 0;
    return (currentIndex + 1) % items.length;
  }, [currentIndex, items]);


  useEffect(() => {
    // 如果只有一个项目或没有项目,则不执行动画
    if (items.length <= 1) {
      return;
    }

    const intervalId = setInterval(() => {
      setIsAnimating(true);
    }, speed);

    // 组件卸载时清除定时器
    return () => clearInterval(intervalId);
  }, [items.length, speed]);

  /**
   * 当退出元素的 CSS 过渡动画结束时调用此函数
   * 它负责重置状态以准备下一个动画循环:
   * 1. 更新当前显示的索引
   * 2. 切换活动槽位
   * 3. 重置动画标志
   */
  const handleTransitionEnd = () => {
    setCurrentIndex(nextIndex);
    setIsEven(prev => !prev);
    setIsAnimating(false);
  };

  // 如果没有项目,返回空或占位组件
  if (!items || items.length === 0) {
    return null;
  }

  // 根据 isEven 状态确定每个槽位应该显示哪个项目
  const evenItem = isEven ? items[currentIndex] : items[nextIndex];
  const oddItem = isEven ? items[nextIndex] : items[currentIndex];

  // 根据当前状态为每个槽位确定正确的 CSS 类名,控制动画效果
  const evenSlotClass = isAnimating
    ? (isEven ? styles.slotExit : styles.slotEnter)
    : (isEven ? styles.slotActive : styles.slotNext);

  const oddSlotClass = isAnimating
    ? (isEven ? styles.slotEnter : styles.slotExit)
    : (isEven ? styles.slotNext : styles.slotActive);

  return (
    <div className={styles.swiperContainer} title="NewSwiper Container">
      <div
        key="even" // 使用静态 key,因为我们要复用 DOM 元素
        className={`${styles.swiperSlot} ${evenSlotClass}`}
        // 只在元素退出视图时添加过渡结束事件处理程序
        onTransitionEnd={isAnimating && isEven ? handleTransitionEnd : undefined}
      >
        {evenItem?.content}
      </div>
      <div
        key="odd" // 使用静态 key,因为我们要复用 DOM 元素
        className={`${styles.swiperSlot} ${oddSlotClass}`}
        onTransitionEnd={isAnimating && !isEven ? handleTransitionEnd : undefined}
      >
        {oddItem?.content}
      </div>
    </div>
  );
};

export default NewSwiper;
css 复制代码
.swiperContainer {
  position: relative;
  height: 40px;
  width: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f8f8f8;
  border-radius: 8px;
  color: #333;
}

.swiperSlot {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
}

.slotNext {
  opacity: 0;
  transform: translateY(100%);
}

.slotActive {
  opacity: 1;
  transform: translateY(0%);
}

.slotEnter {
  opacity: 1;
  transform: translateY(0%);
}

.slotExit {
  opacity: 0;
  transform: translateY(-100%);
}
相关推荐
gxp12322 分钟前
初学React:请求数据参数未更新 && 数据异步状态更新问题
react.js
柳杉1 小时前
从零打造 AI 全球趋势监测大屏
前端·javascript·aigc
simple_lau1 小时前
Cursor配置MasterGo MCP:一键读取设计稿生成高还原度前端代码
前端·javascript·vue.js
睡不着先生1 小时前
如何设计一个真正可扩展的表单生成器?
前端·javascript·vue.js
进击的尘埃1 小时前
AI 代码审查工具链搭建:用 AST 解析 + LLM 实现自动化 Code Review 的前端工程方案
javascript
juejin_cn1 小时前
[转][译] 从零开始构建 OpenClaw — 第五部分(对话压缩)
javascript
leolee182 小时前
Redux Toolkit 实战使用指南
前端·react.js·redux
bluceli2 小时前
React Hooks最佳实践:写出优雅高效的组件代码
前端·react.js
willow3 小时前
Promise由浅入深
javascript·promise
董员外3 小时前
LangChain.js 快速上手指南:Tool的使用,给大模型安上了双手
前端·javascript·后端