用140代码写一个vue移动端轮播图组件

2,452 阅读3分钟

轮播图在项目中,是属于必不可少的一环,基本每个项目都会有,它们的实现原理你知道吗?我们能简单的从原理出发,模拟一个呢?说干就干

首先来看看我实现的效果,全屏宽:

非全屏宽,局部效果:

看起来还是不错的,现在让我们来开始吧!

首先是 - html部分

<template>
<div class="swiper" ref="swiper">
  <div class="swiper-list" :style="{width: swiperListWidth + 'px', transform: 'translateX(' + translateX + 'px)', transitionDuration: transitionDuration + 's' }" ref="swiperList">
    <slot></slot>
  </div>
  <div class="dot">
    <span v-for="(x, i) in sum" :key="'dot' + x" :class="{'on': i === index}"></span>
  </div>
</div>
</template>

这里使用了<slot>插槽,这样可以由父组件直接操作,而不需要传入反复组件传参

css 部分

<style lang="less">
.swiper {
  position: relative;
  overflow: hidden;
  .swiper-list {
    display: flex;
    transition-property: all;
    transition-timing-function: cubic-bezier(0.18, 0.89, 0.32, 1.28);
  }
  .swiper-item {
    width: 100%;
  }
  img {
    display: block;
  }
  .dot {
    display: flex;
    position: absolute;
    width: 100%;
    margin-top: -15px;
    justify-content: center;
    span {
      @size: 8px;
      width: @size;
      height: @size;
      background-color: rgba(255, 255, 255, .5);
      border-radius: 50%;
      &.on {
        background-color: #fff;
      }
      & + span {
        margin-left: 5px;
      }
    }
  }
}
</style>

我习惯使用less进行css编写,在本组件中,我使用了flex布局,而没有使用float布局,因为张鑫旭大大曾说过,浮动是魔鬼啊。 我在此并没有打开vue的scoped属性,虽然这样可能会造成和其他组件冲突,由于我的业务比较简单,而且打开scoped就不能由本组件控制父组件的样式,所以就不打开scoped属性了,当然为了防止冲突,还可以设立自己的命名空间,比如把轮播图的类名设为ali-swiper,避免重复特殊命名

最最重要的JS部分

<script>
export default {
  name: 'swiper',
  data () {
    return {
      swiperWidth: '', // 轮播图盒子的宽度
      index: 0, // 轮播图序号
      transitionDuration: 0.5, // 切换动画时长
      timer: '', // 定时器
      startX: '', // touchstart的起始x坐标
      offset: '' // move偏移值
    }
  },
  props: {
    // 我在这里设置了必填的一个属性,为了不去计算轮播图的总数量
    sum: {
      type: Number,
      required: true
    },
    time: {
      type: Number,
      default: 3000
    }
  },
  computed: {
    // 轮播图列表的宽度
    swiperListWidth () {
      return this.swiperWidth * this.sum
    },
    // 轮播图列表偏移值
    translateX () {
      return this.index * this.swiperWidth * -1
    }
  },
  created () {
    this.$nextTick(() => {
      let swiper = this.$refs.swiper
      // 为什么不取屏幕宽度,是因为通用性,由外部的盒子决定轮播图的宽 
      this.swiperWidth = swiper.offsetWidth
      this.autoPlay()
      // addEventListener不可以用匿名函数,因为无法解除绑定
      swiper.addEventListener('touchstart', this.touchStart)
      swiper.addEventListener('touchmove', this.touchMove)
      swiper.addEventListener('touchend', this.touchEnd)
    })
  },
  methods: {
    autoPlay () {
      this.timer = setInterval(() => {
        let index = this.index + 1
        // 取余数运算,0%5=0,1%5=1,5%5=0,当然用if判断语句也是可以的
        this.index = index % this.sum
      }, this.time)
    },
    touchStart (e) {
      this.transitionDuration = 0
      clearInterval(this.timer)
      // 只记录第一根手指触发的值
      this.startX = e.targetTouches[0].clientX
    },
    touchMove (e) {
      this.offset = this.startX - e.targetTouches[0].clientX
      this.$refs.swiperList.style.transform = `translateX(${this.translateX - this.offset}px)`
    },
    touchEnd (e) {
      this.transitionDuration = 0.5
      // 计算偏移值四舍五入,如果拖动距离大于等于0.5则换一张轮播图
      let num = Math.round(this.offset / this.swiperWidth)
      let sum = this.index + num
      // 先计算再赋值给this.index避免重复触发计算属性,为什么这里不取余数,是因为又负数出现
      if (sum > this.sum - 1) {
        sum = 0
      } else if (sum < 0) {
        sum = this.sum - 1
      }
      // 解决拖动距离小于一半,index值无变化,无法触发计算属性,主动还原偏移值
      if (sum === this.index) {
        this.$refs.swiperList.style.transform = `translateX(${this.translateX}px)`
      } else {
        this.index = sum
      }
      this.autoPlay()
    }
  },
  // 实例销毁之前,移除绑定事件
  beforeDestroy () {
    let swiper = this.$refs.swiper
    swiper.removeEventListener('touchstart', this.touchStart)
    swiper.removeEventListener('touchmove', this.touchMove)
    swiper.removeEventListener('touchend', this.touchEnd)
  }
}
</script>

main.js全局注册组件

import swiper from './assembly/swiper.vue'
Vue.component('swiper', swiper)

我在本项目中使用的是全局注册组件,因为这个轮播图需要使用的地方有点多。如果你的项目中轮播图使用的很少,那我推荐局部注册。

父组件调用轮播图

<div class="box">
    <swiper :sum="5">
      <router-link to="/goods/1" v-for="x of 5" :key="x" class="swiper-item">
        <img src="https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2017/12/27/1609723e9449c30e~tplv-t2oaga2asx-image.image">
      </router-link>
    </swiper>
</div>

你可能发现了,我在组件外嵌套了一层box,这是因为我用的是这个box来控制轮播图的宽度。

至此,一个轮播图组件就完成啦,是不是很简单?