无缝轮播图
vue3 无缝轮播图
1.子组件中
<template>
<div class="carousel-conainer" @touchend="handleTouchEnd" @touchstart="handleTouchStart">
<div
class="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">
<span
v-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.offsetHeight
moveTo(0)
} else {
moveTo((currentIndex.value + 1) % props.items.length)
}
}
const prevSlide = () => {
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.offsetHeight
moveTo(count.value - 1)
}else{
moveTo((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>
2. 父组件中
<script setup>
import Carousel from './components/M-Carousel.vue';
const carouselItems = [
{ id: 1, image: 'https://picsum.photos/id/1018/800/400', title: 'Nature 1' },
{ id: 2, image: 'https://picsum.photos/id/1015/800/400', title: 'Nature 2' },
{ id: 3, image: 'https://picsum.photos/id/1019/800/400', title: 'Nature 3' },
];
</script>
<template>
<div>
<h1>Vue 3 Seamless Carousel</h1>
<Carousel :items="carouselItems" :autoplay-interval="3000" />
</div>
</template>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix