无缝轮播图

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">&lt;</div>
    <div class="carousel-control next" @click="nextSlide">&gt;</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>

 

posted @   献苓  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示