两个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%);
}
相关推荐
尾善爱看海7 分钟前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
李明卫杭州5 小时前
React 复合事件系统对比解析
前端·javascript·react.js
LEE6 小时前
前端转型全栈 01:数据建模,前端最大的盲区
前端·javascript·后端
雪芽蓝域zzs6 小时前
第四十七节:驾驶舱大屏 ECharts 图表集成
前端·javascript·vue.js
梦醒沉醉8 小时前
7、表达日期和时间(前朝遗老Date)
javascript
白雾茫茫丶8 小时前
Vibecoding 一个主题切换动画库:13 种揭幕方式
前端·vue.js·react.js
雪芽蓝域zzs8 小时前
第四十九节:TagsView 右键菜单(带三角箭头)给每个 tag 增加**鼠标右键菜单**(右键标签弹出:关闭、关闭其他、关闭全部)
前端·javascript·vue.js
এ慕ོ冬℘゜8 小时前
从零开发移动端医院挂号页面:适配、交互与前端实现详解
前端·javascript
宁渡AI大模型8 小时前
AI 全栈面试新趋势:Vibe Coding、前端、Java 后端高频面试题深度解析|河南宁渡科技有限公司编程教程
java·javascript·人工智能·python·ai大模型
东风微鸣9 小时前
AI依赖更新别裸奔🚨
javascript