做一個vue輪播圖組件


根據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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM