Vue实现无缝轮播图组件

1,730 阅读1分钟

前言

最近在项目中遇到了轮播图的需求,以往都是直接上框架就完事了。但想想自己还是应该多练练手

思路

  1. 确立样式
    • 父容器限制宽高,超出部分隐藏,设置为弹性布局
    • 子容器利用 flex-shrink: 0 保证容器不被缩小
    • 加上左移,右移按钮和轮播图指示器

pic1.png

  1. 图片滚动
    • 设置一个滚动变量currentIndex(从0开始),表示当前展示的是第几张
    • 设置setInterval让滚动变量自增,并在生命周期函数中调用
    • 设置一个计算属性,返回值为每次滚动的距离:(子容器的宽度+外边距)*滚动变量
    transStyle(){
        return `transform:translateX(${-this.currentIndex*this.itemWidth}px)`
    }
  1. 无缝滚动
    • 在滚动到最后一张图片时,会出现这样的尴尬情况

pic2.png 那么只要我将图片列表复制一份添加在在尾部,并在滚动变量currentIndex==图片数量时,把currentIndex重置为0,同理,左移时如果滚动变量currentIndex==0,将currentIndex置为图片数量-1,这样就算无缝滚动啦。

QQ录屏.gif

html

<template>
  <div class="swipe-wrap">
    <div class="swipe">
      <ul class="viewWrapper">
        <li class="item" :style="transStyle" v-for="item in swipe" :key="item">
          <img :src="item.pic_info.url" alt="" />
        </li>
        <li class="item" :style="transStyle" v-for="item in swipe" :key="item">
          <img :src="item.pic_info.url" alt="" />
        </li>
      </ul>
      <van-icon name="arrow-left" class="icon-left" @click="leftMove" />
      <van-icon name="arrow" class="icon-right" @click="rightMove" />
      <ul class="point-wrapper">
        <li
          v-for="(item, index) in swipe.length"
          :key="item"
          class="circle"
          :class="index == currentIndex ? 'active' : ''"
          @click="onHandle(index)"
        ></li>
      </ul>
    </div>
  </div>
</template>

js

<script>
import { onMounted, reactive, toRefs } from "vue";
export default {
  props: {
    swipe: {
      type: Array,
      default() {
        return [1, 2, 3, 4];
      },
    },
  },
  setup(props) {
    const state = reactive({
      currentIndex: 0, //滚动变量
      autoTime: 2000, //自动播放间隔时长
      wrapperWidth: 860, //整个父容器宽度
      itemWidth: 430, //子容器宽度+外边距
    });
    const onHandle = (e) => {
      state.currentIndex = e;
    };
    const leftMove = () => {
      if (state.currentIndex == 0) {
        state.currentIndex = props.swipe.length - 1;
        return;
      }
      state.currentIndex--;
    };
    const rightMove = () => {
      if (state.currentIndex >= props.swipe.length) return;
      state.currentIndex++;
    };
    const autoMove = () => {
      setInterval(() => {
        state.currentIndex++;
        if (state.currentIndex >= props.swipe.length) {
          state.currentIndex = 0;
        }
      }, state.autoTime);
    };
    onMounted(() => {
      autoMove();
    });
    return {
      autoMove,
      onHandle,
      rightMove,
      leftMove,
      ...toRefs(state),
    };
  },
  computed: {
    transStyle() {
      return `transform:translateX(${-this.currentIndex * this.itemWidth}px)`;
    },
  },
};
</script>

css

<style lang="less" scoped>
.swipe-wrap {
  position: relative;
  .swipe {
    width: 980px;
    .icon-left {
      display: none;
      position: absolute;
      font-size: 50px;
      top: 31%;
      left: -33px;
      z-index: 1000;
    }
    .icon-right {
      display: none;
      position: absolute;
      font-size: 50px;
      top: 31%;
      right: -10px;
    }
    i:hover {
      display: block;
      color: aquamarine;
    }
    .viewWrapper {
      display: flex;

      width: 860px;
      overflow: hidden;
      .item {
        margin: 30px 15px;
        width: 400px;
        height: 200px;
        flex-shrink: 0;
        img {
          width: 100%;
          border-radius: 10px;
        }
      }
    }
    .point-wrapper {
      position: absolute;
      bottom: 12%;
      left: 40%;
      display: flex;
      .circle {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        margin-left: 20px;
        background-color: #969799;
      }
      .active {
        background-color: #111;
      }
      .circle:hover {
        background-color: #07c1608c;
      }
    }
  }
  .swipe:hover {
    i {
      display: block;
    }
  }
}
</style>