两个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%);
}
相关推荐
pe7er2 小时前
React Native 多环境配置全攻略:环境变量、iOS Scheme 和 Android Build Variant
前端·react native·react.js
知识分享小能手2 小时前
Vue3 学习教程,从入门到精通,Vue 3 + Tailwind CSS 全面知识点与案例详解(31)
前端·javascript·css·vue.js·学习·typescript·vue3
柑橘乌云_5 小时前
vue中如何在父组件监听子组件的生命周期
前端·javascript·vue.js
江湖人称小鱼哥6 小时前
react接口防抖处理
前端·javascript·react.js
GISer_Jing6 小时前
腾讯前端面试模拟详解
前端·javascript·面试
萌萌哒草头将军7 小时前
🚀🚀🚀 Webpack 项目也可以引入大模型问答了!感谢 Rsdoctor 1.2 !
前端·javascript·webpack
小白的代码日记7 小时前
Springboot-vue 地图展现
前端·javascript·vue.js
teeeeeeemo7 小时前
js 实现 ajax 并发请求
开发语言·前端·javascript·笔记·ajax
清秋8 小时前
全网最全 ECMAScript 攻略( 更新至 ES2025)
前端·javascript·ecmascript 6
Juchecar9 小时前
Node.js package.json 配置详解 + TypeScript + ES Module 集成指南
javascript