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>
这里使用的图片请自行。我这里使用了自动引入组件,所以不需要额外指定组件。