react实现无缝轮播组件

1.组件定义

TypeScript 复制代码
import React, { useState, useEffect, useCallback } from "react";
import "./style.component.css";

interface SeamlessCarouselProps {
    imageList: string[];
    autoPlaying?: boolean;
}

const SeamlessCarousel: React.FC<SeamlessCarouselProps> = ({
    imageList,
    autoPlaying = true,
}) => {
    const [currentIndex, setCurrentIndex] = useState(1); 
    const [isTransitioning, setIsTransitioning] = useState(true);
    const [isAutoPlaying, setIsAutoPlaying] = useState(autoPlaying);
    const [isPaused, setIsPaused] = useState(false);

    const extendedImages = [
        imageList[imageList.length - 1],
        ...imageList,
        imageList[0],
    ];

    const nextSlide = useCallback(() => {
        setCurrentIndex((prev) => {
            if (prev >= extendedImages.length - 1) {
                return prev;
            }
            return prev + 1;
        });
        setIsTransitioning(true);
    }, []);

    const prevSlide = () => {
        setCurrentIndex((prev) => {
            if (prev <= 0) {
                return prev;
            }
            return prev - 1;
        });
        setIsTransitioning(true);
    };

    useEffect(() => {
        if (!isAutoPlaying || isPaused) return;
        if (!isAutoPlaying) return;
        const interval = setInterval(nextSlide, 3000);
        return () => clearInterval(interval);
    }, [isAutoPlaying, isPaused, nextSlide]);

    useEffect(() => {
        if (currentIndex === extendedImages.length - 1) {
            const timeout = setTimeout(() => {
                setIsTransitioning(false);
                setCurrentIndex(1);
            }, 500);
            return () => clearTimeout(timeout);
        }
        if (currentIndex === 0) {
            const timeout = setTimeout(() => {
                setIsTransitioning(false);
                setCurrentIndex(imageList.length);
            }, 500);
            return () => clearTimeout(timeout);
        }
        setIsTransitioning(true);
    }, [currentIndex, extendedImages.length, imageList.length]);

    return (
        <div
            className={`carousel-container ${isPaused ? "show-arrows" : ""}`}
            onMouseEnter={() => setIsPaused(true)}
            onMouseLeave={() => setIsPaused(false)}
        >
            <div
                className="carousel-list"
                style={{
                    transform: `translateX(-${currentIndex * 100}%)`,
                    transition: isTransitioning
                        ? "transform 0.5s ease"
                        : "none",
                }}
            >
                {extendedImages.map((image, index) => (
                    <div
                        key={index}
                        className="carousel-item"
                        style={{ backgroundImage: `url(${image})` }}
                    ></div>
                ))}
            </div>

            <div
                className="carousel-arrow carousel-arrow-left"
                onClick={prevSlide}
            >
                <span className="arrow-left"></span>
            </div>
            <div
                className="carousel-arrow carousel-arrow-right"
                onClick={nextSlide}
            >
                <span className="arrow-right"></span>
            </div>

            <div className="circles-container">
                {imageList.map((_, index) => (
                    <span
                        key={index}
                        className={`circle ${
                            index === currentIndex - 1 ? "active" : ""
                        }`}
                        onClick={() => setCurrentIndex(index + 1)}
                    ></span>
                ))}
            </div>

            <div className="autoplay-control">
                <button onClick={() => setIsAutoPlaying(!isAutoPlaying)}>
                    {isAutoPlaying ? "暂停" : "播放"}
                </button>
            </div>
        </div>
    );
};

export default SeamlessCarousel;

2.样式设置

css 复制代码
.carousel-container {
    display: flex;
    position: relative;
    align-items: center;
    justify-content: center;
    scroll-behavior: smooth;
    outline: 1px solid #dddedc;
    border-radius: 10px;
    width: 900px;
    height: 600px;
    margin: 30px auto;
    overflow: hidden;
}

.carousel-list {
    display: flex;
    position: relative;
    height: 100%;
    width: 100%;
    scroll-snap-align: start;
    aspect-ratio: 5 / 3;
}

.carousel-item {
    flex: 0 0 100%;
    height: 100%;
    background-size: cover;
    background-repeat: no-repeat;
    background-position: center;
}

.carousel-arrow {
    position: absolute;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.7);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    z-index: 10;
    transition: opacity 0.3s ease, background 0.3s ease;
    opacity: 0;
}

.carousel-container.show-arrows .carousel-arrow {
    opacity: 1; /* 悬停时显示 */
}

.carousel-arrow:hover {
    background: rgba(255, 255, 255, 0.9);
}

.carousel-arrow-left {
    left: 10px;
}

.carousel-arrow-right {
    right: 10px;
}

.arrow-left,
.arrow-right {
    width: 12px;
    height: 12px;
    border-top: 2px solid #333;
    border-right: 2px solid #333;
}

.arrow-left {
    transform: rotate(-135deg);
    margin-left: 4px;
}

.arrow-right {
    transform: rotate(45deg);
    margin-right: 4px;
}

/* 圆点指示器 */
.circles-container {
    display: flex;
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    gap: 20px;
}

.circle {
    display: block;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: transparent;
    border:1px solid rgba(255, 255, 255, 0.7);
    cursor: pointer;
    transition: all 0.3s ease;
}

.circle.active {
    background-color: rgb(249, 246, 246);
    border-color: white;
}

.circle:hover {
    background-color: rgba(255, 255, 255, 0.5);
}

.autoplay-control {
    position: absolute;
    top: 15px;
    right: 15px;
}

.autoplay-control button {
    background: rgba(0, 0, 0, 0.2);
    color: white;
    border: none;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
}

.autoplay-control button:hover {
    background: rgba(0, 0, 0, 0.7);
}

3.组件使用

TypeScript 复制代码
import SeamlessCarousel from "@/components/carousel/SeamlessMo"

import one from "@/components/carousel/img/abstract-2512412.jpg";
import two from "@/components/carousel/img/ai-generated-8061340.jpg";
import three from "@/components/carousel/img/asian-422700.jpg";
import four from "@/components/carousel/img/binary-978942.jpg";
import five from "@/components/carousel/img/code-113611.jpg";
import six from "@/components/carousel/img/fruit-7048114.jpg";
import seven from "@/components/carousel/img/moss-4930309.jpg";
import eight from "@/components/carousel/img/wood-591631.jpg";

const sampleImages = [one, two, three, four, five, six, seven, eight];

const CarouselPage: React.FC = () => {
    return (
        <>
            <h1>无缝轮播页面</h1>
            <div className="mb-4">
                <SeamlessCarousel imageList={sampleImages} autoPlaying={false} />
            </div>
        </>
    )
}

export default CarouselPage;

4.使用测试

需要用到的图片请自行放到对应的目录,再将图片换成实际的路径即可。

相关推荐
Nan_Shu_6141 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#9 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界25 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路34 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug37 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213839 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子2 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端