vue3自定义无缝轮播组件
1.组件定义
<template><div:class="['carousel-container', isPaused ? 'show-arrows' : '']"@mouseenter="isPaused = true"@mouseleave="isPaused = false"><divclass="carousel-list":style="{transform: `translateX(-${currentIndex * 100}%)`,transition: isTransitioning ? 'transform 0.5s ease' : 'none',}"><divv-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"><spanv-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>
这里使用的图片请自行。我这里使用了自动引入组件,所以不需要额外指定组件。