Vue3/Nuxt3实现一个弧形轮播图

主要是使用css实现,做成一个圆形轮播,每个卡片4度,共可以循环90个卡片,循环的卡片数量需要能被90整除,否则需要重新划分卡片旋转度数。缺点是没有显示在可见范围内的卡片数量很多,可能需要用虚拟列表方式优化,有知道怎么优化的小伙伴吗

完整demo可在GitHub - linshujuan/ArcCarousel查看

<template>
  <div>
    <div
      class="relative carousel-container"
      @mousedown.prevent="startDrag"
      @touchstart.prevent="startDrag"
      @mousemove.prevent="handleDragMove"
      @touchmove.prevent="handleDragMove"
      @mouseup.prevent="endDrag"
      @touchend.prevent="endDrag"
    >
      <div class="inner" :style="{ transform: `rotate(${-rotation}deg) ` }" :class="{ animation: animation }">
        <div v-for="(item, index) in allPictures" :key="item" class="item" :class="{ 'mt-[-14px]': currentSlide === index }">
          <img :src="item" alt="" srcset="" :title="index + 1" v-if="displayPosition(index)" :class="{ 'scale-95': currentSlide !== index }" />
        </div>
      </div>
      <div v-show="pictures?.length > 0" class="flex items-center justify-center my-[20px] mb-10 w-full z-[100] absolute top-[600px]">
        <!-- position:{{ position }} //rotation:{{ rotation }} // currentSlide:{{ currentSlide }} -->
        <span path="arrow_left_straight" type="svg" :w="18" :h="16" class="cursor-pointer text-blue mr-[26px]" @click="prev">prev</span>
        <div class="flex items-center">
          <div
            v-for="(item, index) in pictures?.length"
            :key="index"
            class="rounded-full cursor-pointer w-[12px] h-[12px] bg-zinc-500 mr-[10px]"
            :class="{ '!bg-pink w-[16px] h-[16px]': currentSlide % 5 === index }"
            @click="clickHandle(index)"
          ></div>
        </div>
        <span path="arrow_left_straight" type="svg" :w="18" :h="16" class="cursor-pointer text-blue ml-[26px]" @click="next">next</span>
        <!-- <ms-icon path="arrow_left_straight" type="svg" :w="18" :h="16" class="transform rotate-180 cursor-pointer text-blue ml-[16px]" @click="next" /> -->
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  const pictures = ref(['https://picsum.photos/id/50/600', 'https://picsum.photos/id/51/600', 'https://picsum.photos/id/52/600', 'https://picsum.photos/id/53/600', 'https://picsum.photos/id/54/600']);

  const currentSlide = ref(0);
  const rotation = ref(0);
  const position = ref(0);
  const dragStartX = ref(0);
  const isDragging = ref(false);
  const total = ref(pictures.value?.length);
  const animation = ref(true);
  const pDeg = ref(4); // The rotate degree per picture
  const allTotal = ref(360 / pDeg.value);

  const allPictures = computed(() => {
    const all = [] as Array<string>;
    for (let index = 0; index < allTotal.value / total.value; index++) {
      pictures.value.forEach((element) => {
        all.push(element);
      });
    }
    return all;
  });

  const displayPosition = (index: number) => {
    // return index < 6 || index > 88;
    // return index < 7 || index > 86;
    return true;
    return index < 7 || index > 83;
  };

  const prev = () => {
    if (currentSlide.value === 0) {
      currentSlide.value = allTotal.value - 1;
    } else {
      currentSlide.value--;
    }
    rotation.value -= pDeg.value;
  };

  const next = () => {
    if (currentSlide.value === allTotal.value - 1) {
      currentSlide.value = 0;
    } else {
      currentSlide.value++;
    }
    rotation.value += pDeg.value;
  };

  const clickHandle = (index: number) => {
    const steps = index - (currentSlide.value % 5);
    currentSlide.value += steps;
    rotation.value += pDeg.value * steps;
  };

  let rotationClone: number;
  const startDrag = (event) => {
    isDragging.value = true;
    dragStartX.value = getEventX(event);
    rotationClone = Number(JSON.parse(JSON.stringify(rotation.value)));
  };

  const tempRotate = ref(0);
  const handleDragMove = (event) => {
    if (isDragging.value) {
      rotation.value = rotationClone;
      const dragDelta = getEventX(event) - dragStartX.value;
      position.value = -dragDelta;
      tempRotate.value = position.value / 122; // $size/pDeg
      rotation.value = rotation.value + tempRotate.value;
    }
  };

  const endDrag = () => {
    rotation.value = rotationClone;
    if (isDragging.value) {
      isDragging.value = false;
      const tolerance = 50;
      if (position.value >= tolerance) {
        next();
      }
      if (position.value <= -tolerance) {
        prev();
      }
      position.value = 0;
    }
  };

  const getEventX = (event) => {
    return event.clientX || (event.touches && event.touches[0].clientX);
  };
</script>

<style lang="scss" scoped>
  @use 'sass:math';

  $size: 488px; // The slider card height
  $n: 90; // The allTotal value
  $pDeg: calc(360deg / $n);
  $r: calc($size / 2);
  $R: calc($r / math.sin(calc($pDeg / 2))); // This is the formula for calculating the radius of an isosceles triangle
  $innerSize: calc($R * 2);

  .carousel-container {
    // width: $size;
    height: calc($size + 680px);
    margin: 0 auto;
    display: flex;
    justify-content: center;
    overflow: hidden;
    margin-top: 50px;
    padding-top: 40px;
  }

  .inner {
    width: $innerSize;
    height: $innerSize;
    border-radius: 50%;
    flex-shrink: 0;
    margin-top: $r;
    position: relative;
    .item {
      width: calc($size - 80px);
      height: $size;
      position: absolute;
      left: 50%;
      margin-left: calc(math.div(-$size, 2) + 40px);
      top: math.div(-$size, 2);
      transform-origin: center #{$r + $R};
      overflow: hidden;
      @for $i from 1 through $n {
        &:nth-child(#{$i}) {
          transform: rotate($pDeg * ($i - 1));
        }
      }
      img {
        width: 100%;
        height: 100%;
        border-radius: 24px;
        //box-shadow: 0 0 5px 7px rgba(255, 255, 255, 0.5);
      }
    }
  }

  $u: calc(1 / $n * 100%);
  $stopPercent: 0.6 * $u;
  // @keyframes moving {
  //  @for $i from 1 through $n {
  // $p: calc($u * $i);
  // $deg: calc($pDeg * $i);
  // #{$p - $stopPercent},
  //  #{$p} {
  //   transform: rotate(-$deg);
  //    }
  //  }
  // }

  .inner {
    &.animation {
      transition: transform 0.4s linear;
    }
  }
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值