<script setup>
import { onMounted, onUnmounted, ref, useSlots } from 'vue'
const slots = useSlots()
const slides = ref([])
const originalSlidesLength = ref(0)
const currentIndex = ref(0)
const isDragging = ref(false)
const startX = ref(0)
const currentTranslate = ref(0)
const prevTranslate = ref(0)
const props = defineProps({
autoPlay: {
type: Boolean,
default: false
}
})
onMounted(() => {
const originalSlides = slots.default()[0].children.map((slide, index) => ({ content: slide, key: index }))
originalSlidesLength.value = originalSlides.length
slides.value = [...originalSlides, originalSlides[0]]
startAutoPlay()
})
let autoPlayInterval
const startAutoPlay = () => {
if (!props.autoPlay) return
autoPlayInterval = setInterval(() => {
goToNextSlide()
}, 3000)
}
const goToNextSlide = () => {
if (currentIndex.value < slides.value.length - 2) {
currentIndex.value += 1
} else {
currentIndex.value = 0
updateTranslate(true)
return
}
updateTranslate()
}
const startDrag = (event) => {
isDragging.value = true
startX.value = event.clientX
prevTranslate.value = currentTranslate.value
clearInterval(autoPlayInterval)
}
const onDrag = (event) => {
if (isDragging.value) {
const currentX = event.clientX
const diff = currentX - startX.value
currentTranslate.value = prevTranslate.value + (diff / window.innerWidth) * 100
}
}
const endDrag = () => {
if (isDragging.value) {
isDragging.value = false
const movedBy = currentTranslate.value - prevTranslate.value
if (movedBy < -10 && currentIndex.value < slides.value.length - 2) {
currentIndex.value += 1
} else if (movedBy > 10 && currentIndex.value > 0) {
currentIndex.value -= 1
} else if (movedBy > 10 && currentIndex.value === 0) {
currentIndex.value = slides.value.length - 2
} else if (movedBy < -10 && currentIndex.value === slides.value.length - 2) {
currentIndex.value = 0
}
updateTranslate()
startAutoPlay()
}
}
const updateTranslate = (instant = false) => {
prevTranslate.value = -currentIndex.value * 100
currentTranslate.value = prevTranslate.value
if (instant) {
requestAnimationFrame(() => {
currentTranslate.value = prevTranslate.value
})
}
}
const goToSlide = (index) => {
currentIndex.value = index
updateTranslate()
}
onUnmounted(() => {
clearInterval(autoPlayInterval)
})
</script>
<template>
<div class="carousel-container">
<div
class="carousel"
@mousedown="startDrag"
@mousemove="onDrag"
@mouseup="endDrag"
@mouseleave="endDrag"
:style="{ transform: `translateX(${currentTranslate}%)`, transition: isDragging ? 'none' : 'transform 0.3s ease' }"
>
<div v-for="(slide, index) in slides" :key="slide.key" class="slide">
<component :is="slide.content"></component>
</div>
</div>
<div class="indicators">
<span
v-for="(indicator, index) in slides.slice(0, originalSlidesLength)"
:key="index"
class="indicator"
:class="{ active: currentIndex === index || (currentIndex === slides.length - 1 && index === 0) }"
@click="goToSlide(index)"
></span>
</div>
</div>
</template>
<style scoped>
.carousel-container {
position: relative;
width: 100%;
height: 100%;
margin: 0 auto;
overflow: hidden;
padding-bottom: 30px;
}
.carousel {
height: 100%;
display: flex;
}
.slide {
min-width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 2em;
color: white;
user-select: none;
}
.indicators {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
}
.indicator {
width: 10px;
height: 6px;
background-color: rgba(255, 255, 255, 0.5);
cursor: pointer;
transition: background-color 0.3s ease;
}
.indicator.active {
background-color: rgba(255, 255, 255, 1);
}
</style>