react实现无缝轮播组件

发布于:2025-09-12 ⋅ 阅读:(19) ⋅ 点赞:(0)

1.组件定义

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.样式设置

.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.组件使用

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.使用测试

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


网站公告

今日签到

点亮在社区的每一天
去签到