vue3自定义无缝轮播组件

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

1.组件定义

<template>
    <div
        :class="['carousel-container', isPaused ? 'show-arrows' : '']"
        @mouseenter="isPaused = true"
        @mouseleave="isPaused = false"
    >
        <div
            class="carousel-list"
            :style="{
                transform: `translateX(-${currentIndex * 100}%)`,
                transition: isTransitioning ? 'transform 0.5s ease' : 'none',
            }"
        >
            <div
                v-for="(image, index) in extendedImages"
                :key="index"
                class="carousel-item"
                :style="{ backgroundImage: `url(${image})` }"
            ></div>
        </div>

        <div class="carousel-arrow carousel-arrow-left" @click="prevSlide">
            <span className="arrow-left"></span>
        </div>
        <div class="carousel-arrow carousel-arrow-right" @click="nextSlide">
            <span className="arrow-right"></span>
        </div>

        <div class="circles-container">
            <span
                v-for="(_, index) in props.imageList"
                :key="index"
                :class="['circle', index === currentIndex - 1 ? 'active' : '']"
                @click="currentIndex = index + 1"
            >
            </span>
        </div>

        <div class="autoplay-control">
            <button @click="isPaused == !isAutoPlaying">
                {{ isAutoPlaying ? "暂停" : "播放" }}
            </button>
        </div>
    </div>
</template>
<script lang="ts" setup>
const props = defineProps({
    imageList: {
        type: Array as PropType<string[]>,
        required: true,
    },
    interval: {
        type: Number,
        default: () => 2000,
    },
    autoPlay: {
        type: Boolean,
        default: () => false,
    },
});


const currentIndex = ref<number>(1);
const isTransitioning = ref<boolean>(true);
const isAutoPlaying = ref<boolean>(props.autoPlay);
const isPaused = ref<boolean>(false);


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

const nextSlide = () => {
    currentIndex.value += 1;
    isTransitioning.value = true;
};

const prevSlide = () => {
    currentIndex.value -= 1;
    isTransitioning.value = true;
};


watchEffect(onCleanup => {
    if (isAutoPlaying.value && !isPaused.value) {
        const timer = setInterval(nextSlide, props.interval);
        onCleanup(() => clearInterval(timer));
    }
});


watch(currentIndex, newIndex => {
    if (newIndex === extendedImages.length - 1) {
        setTimeout(() => {
            isTransitioning.value = false;
            currentIndex.value = 1;
        }, 500);
    }
    if (newIndex === 0) {
        setTimeout(() => {
            isTransitioning.value = false;
            currentIndex.value = props.imageList.length;
        }, 500);
    }
});
</script>
<style scoped>
.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);
}
</style>

2.组件注册

在当前组件同目录创建index.ts,内容如下

import type { App } from "vue";
import SeamlessCarousel from "./index.vue";

export default {
    install(app: App) {
        app.component("SeamlessCarousel", SeamlessCarousel);
    },
};

3.组件引入

在src的components目录下的index.ts中添加

import type { App } from "vue";
import SeamlessCarousel from "./SeamlessCarousel"

const components = [
    SeamlessCarousel,
];

export default {
    install(app: App) {
        components.map(item => {
            app.use(item);
        });
    },
};

4.组件使用

<template>
	<div class="flex w-full mt-6">
		<SeamlessCarousel :imageList="sampleImages" :interval="5000" :autoPlay="true"/>
	</div>
</template>

<script lang="ts" setup>

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

const sampleImages = [one, two, three, four, five, six, seven, eight];
</script>

这里使用的图片请自行。我这里使用了自动引入组件,所以不需要额外指定组件。

5.组件测试


网站公告

今日签到

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