两个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%);
}
相关推荐
程序员老赵1 小时前
Docker 部署 ZLMediaKit:轻松搭建高性能流媒体服务平台
前端·javascript·后端
拾年2751 小时前
前后端分离开发,别再"傻等"后端接口了——聊聊前端接口工程
前端·javascript·react.js
SendTomo1 小时前
SendTomo稳定传输三大核心策略
javascript·网络协议·webrtc·html5·p2p
天才熊猫君2 小时前
从“改自己”到“改布局”——拖拽调整的通用设计思路
前端·javascript
weixin_BYSJ19872 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php
默_笙2 小时前
🍳 受控组件和非受控组件,我纠结了一整天,最后用"房东和租客"讲明白了
前端·javascript
半个落月2 小时前
React 受控组件与非受控组件详解:从输入框到表单校验
前端·react.js
Hilaku2 小时前
高级前端如何优雅地拒绝不合理的产品需求?
前端·javascript·程序员
独立开阀者_FwtCoder2 小时前
这次更新:换新主题,同时把好计划分享出去
前端·javascript·vue.js
禁止摆烂_才浅2 小时前
JavaScript WebAPI(进阶)高频面试题
前端·javascript·面试