欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

跟着做一个vue轮播图组件

程序员文章站 2022-07-11 17:14:16
根据huangyi老师的慕课网vue项目跟着做的,下面大概记录了下思路 1.轮播图的图 先不做轮播图逻辑部分,先把数据导进来,看看什么效果。在recommend组件新建一个recommends的数组,用这个数组来接受数据的录播图部分。然后再轮播图的插槽部分添加图片,代码如下 但是现在轮播图是糊的,所 ......

根据huangyi老师的慕课网vue项目跟着做的,下面大概记录了下思路

1.轮播图的图

先不做轮播图逻辑部分,先把数据导进来,看看什么效果。在recommend组件新建一个recommends的数组,用这个数组来接受数据的录播图部分。然后再轮播图的插槽部分添加图片,代码如下

<slider>
   <div v-for="(item,index) in recommends" :key="index">
       <a :href="item.linkurl">
          <img :src="item.picurl">
       </a>
    </div>
 </slider>
// recommends.vue
<script>
  data() {
    return {
      recommends: []
    }
},
methods: {
   _getrecommend() {
     getrecommend().then(res => {
       if (res.code === err_ok) {
         this.recommends = res.data.slider
         console.log(this.recommends)
       }
      )
   }
},
</script>

但是现在轮播图是糊的,所以就要按着需求来自己做slider组件。

首先,我们给轮播图slidergroup,设置一个总的宽度。

<div class="slider" ref="slider">
    <div class="slider-group" ref="slidergroup">
      <slot></slot>
    </div>
    <div class="dots"></div>
  </div>

要设置slidergroup的宽度的话,我们要在渲染好dom元素的时候再设置宽度,所以我们要在mouted这个钩子函数里执行设置宽度,_setsliderwidth()和 _initslider()分别是设置宽度和加入滚动效果。这里是为了分离,不让mounted这个钩子函数里有太多东西,然后不好改逻辑。

mounted() {
    settimeout(() => {
      this._setsliderwidth()
      this._initslider()
    }, 20)
 },
 

下面就是设置slidergroup的宽度,其实中我们设置的主要方法,就是把slider的宽度给slidergroupd的每个children,其中的slider-item属性是让他们左浮动的。然后让他们超出来的都隐藏掉。然后最后因为loop是循环轮播,要给slider前后各加一个宽度,这个是基础了,不懂得百度轮播图原理。然后最后让slidergroup的宽度变成通过slot传进来的图片加2的宽度。

methods: {
    _setsliderwidth() {
      this.children = this.$refs.slidergroup.children

      let width = 0
      let sliderwidth = this.$refs.slider.clientwidth
      for (let i = 0; i < this.children.length; i++) {
        let child = this.children[i]
        addclass(child, 'slider-item')

        child.style.width = sliderwidth + 'px'
        width += sliderwidth
      }
      if (this.loop) {
        width += 2 * sliderwidth
      }
      this.$refs.slidergroup.style.width = width + 'px'
    }
}

addclass方法不是系统自带的,是自己定义的,放在项目的src/common/js/dom/js里

export function addclass(el, classname) {
  if (hasclass(el, classname)) {
    return
  }

  let newclass = el.classname.split(' ')
  newclass.push(classname)
  el.classname = newclass.join(' ')
}

export function hasclass(el, classname) {
  let reg = new regexp('(^|\\s)' + classname + '(\\s|$)')
  return reg.test(el.classname)
}

在设置完宽度以后,需要在recommend.vue设置一下加入addclass的时间,因为getrecommend这个方法是异步的,所以如果在dom渲染完后的时候在执行addclass方法,此时还没有获得到数据,所以也就没有slot里面的数据,所以要在slder组件外侧的div中设置一个v-if

<div v-if="recommends.length" class="slider-wrapper">
        <slider>
          <div v-for="(item,index) in recommends" :key="index">
            <a :href="item.linkurl">
              <img :src="item.picurl">
            </a>
          </div>
        </slider>
      </div>

当轮播图可以正确显示的时候,我们需要给轮播图添加滑动。我们用better-scroll,直接在npm上安装,然后在script标签里引入bscroll, 然后传入合适的参数,就可以了。

 _initslider() {
      this.slider = new bscroll(this.$refs.slider, {
        scrollx: true,
        scrolly: false,
        momentum: false,
        snap: true,
        snaploop: this.loop,
        snapthreshold: 0.3,
        snapspeed: 400,
        click: true
      })
    }

2.轮播图的dots

首先,我们要通过children.length来新建dots,在哪里新建呢?在mounted里

mounted() {
    settimeout(() => {
      this._setsliderwidth()
      this._initdots()
      this._initslider()
    }, 20)
}

然后顺应着新建一个_initdots方法,这样可以有效的分离,业务逻辑比较清晰。

_initdots() {
      this.dots = new array(this.children.length)
},

现在的程度是仅仅有dots的静态了(css做出样式),然后我们需要根据页面来设置active-dots。所以我们需要在_initslider()方法中监听scrollend事件,这个时间是better-scroll的,如果没导入就没有。

this.slider.on('scrollend', () => {
   let pageindex = this.slider.getcurrentpage().pagex
   // 这个pageindex -1是因为前面有一张为了无缝连接轮播图的。需要把他弄掉
    if (this.loop) {
      pageindex -= 1
    } 
        this.currentpageindex = pageindex
})

然后配合js,我们在html绑定相应的class就行了。

 <div class="dots">
      <span
        class="dot"
        v-for="(item,index) in dots"
        :key="index"
        :class="{active:currentpageindex === index}"
      ></span>
</div>

这样就就可以实现轮播带着dots一起动的效果了,接下来做自动播放功能

3. 轮播图自动播放

自动播放的时机,就是在新建轮播图完成的时候,也就是在mounted钩子里,定义一个_play方法

 mounted() {
    settimeout(() => {
      this._setsliderwidth()
      this._initdots()
      this._initslider()
      if (this.autoplay) {
        this._play()
      }
    }, 20)
 }

然后我们顺着去找methods里定义_play()这个方法。

_play() {
      let pageindex = this.currentpageindex + 1
      if (this.loop) {
        pageindex += 1
      }
      this.timer = settimeout(() => {
        // 0 代表y方向,400表示间隔
        this.slider.gotopage(pageindex, 0, 400)
      }, this.interval)
}

但是这个在mounted钩子里,我们只调用了依次gotopage方法。这很不爽。所以需要我们在想办法,让每次换页的时候都去调用一下,拿着还不好说嘛,用上次的scrollend事件,所以只需要在上次那个地方添加一些方法就ok了

this.slider.on('scrollend', () => {
        let pageindex = this.slider.getcurrentpage().pagex
        if (this.loop) {
          pageindex -= 1
        }
        this.currentpageindex = pageindex

        if (this.autoplay) {
          cleartimeout(this.timer)
          this._play()
        }
   })

ok,现在轮播图的dots,滑动,自动播放功能就完成了。下面是组件完整的代码

<template>
  <div class="slider" ref="slider">
    <div class="slider-group" ref="slidergroup">
      <slot></slot>
    </div>
    <div class="dots">
      <span
        class="dot"
        v-for="(item,index) in dots"
        :key="index"
        :class="{active:currentpageindex === index}"
      ></span>
    </div>
  </div>
</template>
<script type="text/ecmascript-6">
import bscroll from 'better-scroll'
import { addclass } from 'common/js/dom'
export default {
  data() {
    return {
      dots: [],
      currentpageindex: 0
    }
  },
  props: {
    // 是否可以循环轮播
    loop: {
      type: boolean,
      default: true
    },
    // 是否可以自动轮播
    autoplay: {
      type: boolean,
      default: true
    },
    // 自动轮播时间间隔
    interval: {
      type: number,
      default: 4000
    }
  },
  mounted() {
    settimeout(() => {
      this._setsliderwidth()
      this._initdots()
      this._initslider()
      if (this.autoplay) {
        this._play()
      }
    }, 20)
    window.addeventlistener('resize', () => {
      if (!this.silder) {
        return
      }
      this._setsliderwidth(true)
      this.slider.refresh()
    })
  },
  methods: {
    _setsliderwidth(isresize) {
      this.children = this.$refs.slidergroup.children

      let width = 0
      let sliderwidth = this.$refs.slider.clientwidth
      for (let i = 0; i < this.children.length; i++) {
        let child = this.children[i]
        addclass(child, 'slider-item')

        child.style.width = sliderwidth + 'px'
        width += sliderwidth
      }
      if (this.loop && !isresize) {
        width += 2 * sliderwidth
      }
      this.$refs.slidergroup.style.width = width + 'px'
    },
    _initslider() {
      this.slider = new bscroll(this.$refs.slider, {
        scrollx: true,
        scrolly: false,
        momentum: false,
        snap: true,
        snaploop: this.loop,
        snapthreshold: 0.3,
        snapspeed: 400,
        click: true
      })
      this.slider.on('scrollend', () => {
        let pageindex = this.slider.getcurrentpage().pagex
        if (this.loop) {
          pageindex -= 1
        }
        this.currentpageindex = pageindex

        if (this.autoplay) {
          cleartimeout(this.timer)
          this._play()
        }
      })
    },
    _initdots() {
      this.dots = new array(this.children.length)
    },
    _play() {
      let pageindex = this.currentpageindex + 1
      if (this.loop) {
        pageindex += 1
      }
      this.timer = settimeout(() => {
        // 0 代表y方向,400表示间隔
        this.slider.gotopage(pageindex, 0, 400)
      }, this.interval)
    }
  }
}
</script>
<style scoped lang="stylus" rel="stylesheet/stylus">
@import '~common/stylus/variable'
.slider
  min-height: 1px
  .slider-group
    position: relative
    overflow: hidden
    white-space: nowrap
    .slider-item
      float: left
      box-sizing: border-box
      overflow: hidden
      text-align: center
      a
        display: block
        width: 100%
        overflow: hidden
        text-decoration: none
      img
        display: block
        width: 100%
  .dots
    position: absolute
    right: 0
    left: 0
    bottom: 12px
    text-align: center
    font-size: 0
    .dot
      display: inline-block
      margin: 0 4px
      width: 8px
      height: 8px
      border-radius: 50%
      background: $color-text-l
      &.active
        width: 20px
        border-radius: 5px
        background: $color-text-ll
</style>