You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React无依赖实现环形无限轮播:仿Aniwave站点轮播功能问询

实现无缝无限轮播的解决方案

要实现目标站点那样的无缝无限轮播,将第一张幻灯片追加到末尾、最后一张放到开头的首尾克隆方案是最简洁且实用的选择——直接调整偏移量会出现明显跳变,无法做到视觉上的“无限”效果,而克隆幻灯片+过渡后瞬间重置位置的逻辑,能完美模拟无限滚动的体验。

以下是修改后的完整代码:

import { useRef, useEffect, useState } from "react";
import { details } from "./constants";

const App = () => {
  const carouselTrackRef = useRef(null);
  const containerRef = useRef(null);
  const [counter, setCounter] = useState(1); // 初始指向原第一张(因为前面多了克隆的最后一张)
  const slideWidthRef = useRef(0);

  // 获取幻灯片宽度并监听窗口变化
  useEffect(() => {
    const updateSlideWidth = () => {
      const firstSlide = carouselTrackRef.current?.children[0];
      if (firstSlide) {
        slideWidthRef.current = firstSlide.offsetWidth;
        carouselTrackRef.current.style.left = `-${slideWidthRef.current * counter}px`;
      }
    };
    updateSlideWidth();
    window.addEventListener("resize", updateSlideWidth);
    return () => window.removeEventListener("resize", updateSlideWidth);
  }, [counter]);

  const updateTrackPosition = () => {
    carouselTrackRef.current.style.left = `-${slideWidthRef.current * counter}px`;
  };

  const prevSlide = () => {
    setCounter(prev => prev - 1);
    updateTrackPosition();
  };

  const nextSlide = () => {
    setCounter(prev => prev + 1);
    updateTrackPosition();
  };

  // 过渡结束后处理无缝重置逻辑
  useEffect(() => {
    const handleTransitionEnd = () => {
      const slideCount = details.length;
      // 滚动到克隆的第一张(原最后一张),瞬间跳转到原最后一张
      if (counter === 0) {
        setCounter(slideCount);
        carouselTrackRef.current.style.transition = "none";
        updateTrackPosition();
        // 重新开启过渡动画
        setTimeout(() => {
          carouselTrackRef.current.style.transition = "all 0.5s ease";
        }, 10);
      }
      // 滚动到克隆的最后一张(原第一张),瞬间跳转到原第一张
      else if (counter === slideCount + 1) {
        setCounter(1);
        carouselTrackRef.current.style.transition = "none";
        updateTrackPosition();
        setTimeout(() => {
          carouselTrackRef.current.style.transition = "all 0.5s ease";
        }, 10);
      }
    };

    const track = carouselTrackRef.current;
    track?.addEventListener("transitionend", handleTransitionEnd);
    return () => track?.removeEventListener("transitionend", handleTransitionEnd);
  }, [counter]);

  // 键盘导航事件
  useEffect(() => {
    const handleKeyDown = (e) => {
      if (e.code === "ArrowRight") nextSlide();
      if (e.code === "ArrowLeft") prevSlide();
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, []);

  return (
    <div className="w-screen h-screen flex items-center bg-slate-500">
      <div
        ref={containerRef}
        className="w-screen h-2/3 overflow-x-hidden relative border-cyan-800 border-4"
      >
        <div
          onClick={prevSlide}
          className="absolute cursor-pointer text-2xl text-white z-10 left-0 top-1/2"
        >
          Prev
        </div>
        <div
          onClick={nextSlide}
          className="absolute cursor-pointer text-2xl text-white z-10 right-0 top-1/2"
        >
          Next
        </div>
        <div
          ref={carouselTrackRef}
          className="h-full flex transition-all duration-500 ease-in-out absolute bg-red-800"
        >
          {/* 克隆最后一张幻灯片放到最前面 */}
          <div
            className="w-screen h-full flex-shrink-0"
            key={`clone-last-${details[details.length - 1].title}`}
          >
            <div className="rounded-lg h-full">
              <img
                src={details[details.length - 1].image}
                alt="clone-last"
                className="rounded-lg w-full h-full object-cover"
              />
            </div>
          </div>
          {/* 原幻灯片列表 */}
          {details.map((slide) => (
            <div
              className="w-screen h-full flex-shrink-0"
              key={slide.title}
            >
              <div className="rounded-lg h-full">
                <img
                  src={slide.image}
                  alt={slide.title}
                  className="rounded-lg w-full h-full object-cover"
                />
              </div>
            </div>
          ))}
          {/* 克隆第一张幻灯片放到最后面 */}
          <div
            className="w-screen h-full flex-shrink-0"
            key={`clone-first-${details[0].title}`}
          >
            <div className="rounded-lg h-full">
              <img
                src={details[0].image}
                alt="clone-first"
                className="rounded-lg w-full h-full object-cover"
              />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default App;

核心逻辑说明:

  1. 首尾克隆:在原幻灯片列表前后分别添加最后一张和第一张的克隆项,让用户滚动到边界时看到的是衔接的内容,视觉上形成无限循环的错觉。
  2. 过渡后重置:监听轮播轨道的transitionend事件,当滚动到克隆项时,瞬间取消过渡动画、重置偏移量到真实的首尾幻灯片位置,再重新开启过渡——这个过程极快,用户完全感知不到跳变。
  3. 响应式适配:监听窗口大小变化,实时更新幻灯片宽度并调整轮播位置,保证在不同屏幕尺寸下都能正常工作。
  4. 内存泄漏防护:所有全局事件监听都在组件卸载时移除,避免不必要的资源占用。

内容的提问来源于stack exchange,提问作者Akif Ahmed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 12:58:05