两个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%);
}
相关推荐
_r0bin_9 分钟前
分片上传-
前端·javascript·状态模式
东北南西13 分钟前
手写React状态hook
前端·javascript·react.js
诗书画唱14 分钟前
【前端教程】JavaScript DOM 操作实战案例详解
开发语言·前端·javascript
quan263144 分钟前
Vue实践篇-02,AI生成代码
前端·javascript·vue.js
小桥风满袖2 小时前
极简三分钟ES6 - 类与继承
前端·javascript
子兮曰2 小时前
🚀99% 的前端把 reduce 用成了「高级 for 循环」—— 这 20 个骚操作让你一次看懂真正的「函数式折叠」
前端·javascript·typescript
wifi歪f2 小时前
📦 qiankun微前端接入实战
前端·javascript·面试
小桥风满袖2 小时前
极简三分钟ES6 - Symbol
前端·javascript
子兮曰2 小时前
🚀Map的20个神操作,90%的开发者浪费了它的潜力!最后的致命缺陷让你少熬3天夜!
前端·javascript·ecmascript 6
练习时长两年半的Java练习生(升级中)2 小时前
从0开始学习Java+AI知识点总结-30.前端web开发(JS+Vue+Ajax)
前端·javascript·vue.js·学习·web