## vue3 无缝轮播图
<template><!-- @touchend="handleTouchEnd" --><!-- @touchstart="handleTouchStart" --><div class="carousel-conainer"><divclass="carousel-list":style="{ transform: `translateX(-${currentIndex * 100}%)` }"><div v-for="(item) in items" :key="item.id" class="carousel-item"><img :src="item.image" :alt="item.title" /></div></div><div class="carousel-control prev" @click="prevSlide"><</div><div class="carousel-control next" @click="nextSlide">></div><div class="indicator"><spanv-for="(childItem,childIndex) in items":key="childItem.id":class="{ active: currentIndex === childIndex }"@click="currentIndex = childIndex"></span></div></div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue'const props = defineProps({items: {type: Array,required: true,validator: items => Array.isArray(items) && items.length > 0, // 增加验证,确保items为数组且不为空},autoplayInterval: {type: Number,default: 3000,},
})const currentIndex = ref(0)
// const touchStartX = ref(0)
const isVisible = ref(true)
// let autoplayInterval = null// const updateAutoplay = () => {
// if (isVisible.value && props.autoplayInterval) {
// autoplayInterval = setInterval(nextSlide, props.autoplayInterval)
// } else {
// clearInterval(autoplayInterval)
// }
// }
// const doms = ref({
// carousleList: document.querySelector('.carousel-list'),
// indicator: document.querySelector('.indicator'),
// prevBtn: document.querySelector('.prev'),
// nextBtn: document.querySelector('.next'),
// })
const doms = ref({carousleList: null,indicator: null,
})
const count = ref(props.items.length)
const init = () => {doms.value.carousleList = document.querySelector('.carousel-list')doms.value.indicator = document.querySelector('.indicator')// 复制第一张图片到最后,复制最后一张图片到第一if (doms.value.carousleList) {const firstCloned = doms.value.carousleList.firstElementChild.cloneNode(true)const lastCloned = doms.value.carousleList.lastElementChild.cloneNode(true)doms.value.carousleList.appendChild(firstCloned)doms.value.carousleList.insertBefore(lastCloned,doms.value.carousleList.firstElementChild,)lastCloned.style.marginLeft = '-100%' } else {console.error("Carousel list not found");}// 绑定事件
}const moveTo = index => {doms.value.carousleList.style.transform = `translateX(-${index * 100}%)`doms.value.carousleList.style.transition = 'transform 0.5s ease'doms.value.indicator.querySelectorAll('span')[index].classList.add('active')doms.value.indicator.querySelectorAll('span')[currentIndex.value].classList.remove('active')currentIndex.value = index
}
const nextSlide = () => {if (currentIndex.value === count.value - 1) {doms.value.carousleList.style.transform = `translateX(100%)`doms.value.carousleList.style.transition = 'none'// 让浏览器渲染doms.value.carousleList.offsetHeightmoveTo(0)} else {moveTo((currentIndex.value + 1) % props.items.length)}
}
const prevSlide = () => {// if (props.items.length === 0) return // 防止没有项目时出错if (currentIndex.value === 0) {console.log('currentIndex.value === 0')doms.value.carousleList.style.transform = `translateX(-${count.value * 100}%)`doms.value.carousleList.style.transition = 'none'// 让浏览器渲染doms.value.carousleList.offsetHeightmoveTo(count.value - 1)}else{moveTo((currentIndex.value - 1 + props.items.length) % props.items.length)}
}// const nextSlide = () => {
// if (props.items.length === 0) return // 防止没有项目时出错
// currentIndex.value = (currentIndex.value + 1) % props.items.length
// }// const prevSlide = () => {
// if (props.items.length === 0) return // 防止没有项目时出错
// currentIndex.value =
// (currentIndex.value - 1 + props.items.length) % props.items.length
// }const handleVisibilityChange = () => {isVisible.value = !document.hidden// updateAutoplay()
}// const handleTouchStart = e => {
// if (e.touches.length > 0) {
// touchStartX.value = e.touches[0].clientX
// }
// }// const handleTouchEnd = e => {
// if (e.changedTouches.length > 0) {
// const touchEndX = e.changedTouches[0].clientX
// const diff = touchStartX.value - touchEndX
// if (Math.abs(diff) > 50) {
// diff > 0 ? nextSlide() : prevSlide()
// }
// }
// }onMounted(() => {init()// updateAutoplay()document.addEventListener('visibilitychange', handleVisibilityChange)
})onUnmounted(() => {// clearInterval(autoplayInterval)document.removeEventListener('visibilitychange', handleVisibilityChange)
})// watch(isVisible, updateAutoplay)
</script><style scoped lang="scss">
.carousel-conainer {position: relative;width: 500px;// height: 400px;margin: 50px auto;outline: 1px solid #000;overflow: hidden;.carousel-list {display: flex;transition: transform 0.5s ease;height: 100%;.carousel-item {flex: 0 0 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;align-items: center;}.carousel-item img {max-width: 100%;max-height: 80%;object-fit: contain;}}.carousel-control {position: absolute;top: 50%;width: 40px;height: 40px;transform: translateY(-50%);background: rgba(0, 0, 0, 0.4);border-radius: 50%;color: white;border: none;padding: 10px;cursor: pointer;display: flex;justify-content: center;align-items: center;&:hover {background: rgba(0, 0, 0, 1);}}.carousel-control.prev {left: 10px;}.carousel-control.next {right: 10px;}.indicator {position: absolute;bottom: 10px;left: 50%;transform: translateX(-50%);display: flex;justify-content: center;align-items: center;width: 100%;height: 20px;// background: rgba(0, 0, 0, 0.5);color: white;font-size: 12px;text-align: center;span {margin: 0 5px;width: 10px;height: 10px;border-radius: 50%;background: #fff;border: 1px solid #000;cursor: pointer;&.active {background: red;}}}
}
</style>