一、需求

基于 better-scroll 实现一个移动端使用的 vue 轮播图组件。

better-scroll 的介绍 :

better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心是借鉴的 iscroll 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又扩展了一些 feature 以及做了一些性能优化。

better-scroll 文档地址:

对于 better-scroll 的使用不做过多的介绍,其实基于什么实现都可以,甚至可以自己实现。

better-scroll 对于实现一个轮播或者是滚动非常的方便,因为项目中其他的组件也有使用这个依赖,因此直接使用了 better-scroll。

二、Slider 组件的基本构想(html&css)

props:

  • width -> 图片宽度 默认 100%
  • list -> 数据列表 对象数组,格式 [{linkUrl:'url',picUrl:'url'}]
  • loop -> 是否循环轮播 默认 true
  • autoPlay -> 是否自动轮播 默认 true
  • speed -> 自动轮播速度 默认 3000
  • dots -> 是否显示 dots 默认 true

上面是通过 props 的参数控制,需要注意的是:因为只是做演示,因此图片点击跳转的 linkUrl 直接使用的是
a 标签而不是,vue-routerrouter-link

基本的 dom 组织如下:

slider 是整体的容器,而 slider-groupslider-item 的容器,dotsdot 的容器

<div class="slider" ref="slider">
      <div class="slider-group" ref="sliderGroup">
          <div class="slider-item" v-for="(item,index) in list" :key="index" >
              <a :href="item.linkUrl">
                  <img :src="item.picUrl" :alt="item.linkUrl" :style="`width:${width}`" ref="sliderItemImg">
              </a>
          </div>
      </div>
     <div class="dots" v-if="this.dots">
          <span :class="`dot ${index === currentPageIndex ? 'active' :'' }`" v-for="(item,index) in list.length" :key="index"></span>
      </div>
  </div>

基本的 css 内容:

.slider {
  position: relative;
  min-height: 1px;
}
.slider .slider-group {
  position: relative;
  overflow: hidden;
  white-space: nowrap;
}
.slider .slider-group .slider-item {
  float: left;
  overflow: hidden;
  text-align: center;
}
.slider .slider-group .slider-item a {
  display: block;
  width: 100%;
  overflow: hidden;
  text-decoration: none;
}
.slider .slider-group .slider-item img {
  display: block;
  width: 100%;
}
.slider .dots {
  position: absolute;
  right: 0;
  left: 0;
  bottom: 12px;
  text-align: center;
  font-size: 0;
}
.slider .dot {
  display: inline-block;
  margin: 0 4px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #dfdfdf;
}
.slider .dot.active {
  width: 20px;
  border-radius: 5px;
  background: #dfdfdf;
}

有了 html 和 css 之后,下面就是实现轮播图的逻辑。

这里实现的slider 实际上就是通过 width 进行 overflow:hidden 控制,通过 translate 进行图片移动,而translate 则是通过 better-scroll 实现的

三、组件的功能实现

1、初始化 slider 宽度:_initSliderWidth()

这是一个内部方法,主要是初始化 slider 容器的宽度

主要的功能是:

  • 获取图片的 clientWidth,(因为设置的时候可能使用 100% 设置图片宽度,因此 props width 不能直接使用)
  • 通过图片的 clientWidth 设置 slider 容器的宽度 (如果是 loop = true,无缝轮播的时候根据 better-scroll 的原理,需要多加两个 图片的 clientWidth ,见下面代码的注释)
  • 设置图片的宽度为 clientWidth 因为设置完 slider 容器宽度之后,如果图片还使用 100% 控制宽度,图片就无法再做到 100% 的样式,需要手动去指定图片的宽度。
/**
 * @name _initSliderWidth
 * @description 初始化slider容器Width
 * @author postbird
 */
_initSliderWidth() {
    const imgWidth = this.$refs.sliderItemImg[0].clientWidth;
    let sliderWidth = imgWidth * this.list.length;
    if (this.loop) {
    // 循环轮播需要添加两个img DOM 的宽度
    sliderWidth += imgWidth * 2;
    }
    this.$refs.sliderGroup.style.width = sliderWidth + "px";
    this.$refs.sliderItemImg.forEach(item => {
    item.style.width = imgWidth + "px";
    });
},

2、初始化 better-slider _initSlider()

在设置完 slider 的宽度之后,就可以初始化 better-scroll ,主要的参数可以参照相关的参数文档

因为最终是需要 slider 这个实例的,因此 this._slider 就是私有的 better-scroll 实例

其中,调用了获取当前页的方法 _this._getCurrentPageIndex

监听了 beforeScrollStart 事件,在滚动之前,清楚了 _timer,主要是在滚动之前,将自动滚动事件清除掉(详细需要参考自动滚动的实现)

/**
 * @name _initSlider
 * @description 初始化 better-scroll
 * @author postbird
 */
_initSlider() {
    const _this = this;
    _this._slider = new BScroll(_this.$refs.slider, {
    scrollX: true,
    scrollY: false,
    momentum: false,
    click:true,
    snap: {
        loop: this.loop, // 循环
        threshold: 0.1
    }
    });
    // 获取当前页
    _this._getCurrentPageIndex();
    // 滚动之前 清除timer
    _this._slider.on('beforeScrollStart',()=>{
        clearInterval(_this._timer);
    });
}

3、获取滚动的当前页 _getCurrentPageIndex()

获取当前滚动的页数,主要是用于 dots 中 active class 的添加

通过监听 scrollEnd 事件,获取当前页数,获取方式也是根据better-scroll提供的接口获取的。同时,因为有自动播放的存在,在滚动结束后,如果开启了 autoPlay,则应该重新开启自动播放(因为在 _initSlider 中,滚动之前清楚了 _timer

/**
 * @name _getCurrentPageIndex
 * @description 获取当前的 pageIndex
 * @author postbird
 */
_getCurrentPageIndex() {
    const _this = this;
    _this._slider.on("scrollEnd", () => {
    let index = _this._slider.getCurrentPage().pageX;
    _this.currentPageIndex = index;
    // 如果自动播放 则开启
    if(_this.autoPlay){
        _this._play();
    }
    });
},

4、控制自动轮播 _play()

这个没什么说的,就是通过定时器进行 goToPage() 方法的调用即可

/**
 * @name _play
 * @description 控制自动轮播
 * @author postbird
 */
_play(){
    const _this =this;
    let pageIndex = _this.currentPageIndex;
    _this._timer = setInterval(() => {
        pageIndex++;
        if(pageIndex >= _this.list.length){
            pageIndex = 0;
        }
        _this._slider.goToPage(pageIndex);
    }, this.speed);
}

四、组件的挂载钩子

在实现了上面这几个关键功能之后,就可以进行挂载处理了。

mounted() {
    // $nextTick() 是 vue 提供的钩子,主要是确保 DOM 已经全部挂载完在调用下面的方法
    // 还可以通过 setIimeout 来实现
    // 发现使用 this.$nectTick() 存在问题,因此使用了 settimeout 20ms 的方法
    const _this = this;
    _this.tmpTimer = setTimeout(() => {
         _this._initSliderWidth();
        _this._initSlider();
        if (_this.autoPlay) {
            _this._play();
        }
    }, 20);
  },

五、在其他组件中使用 Slider 组件

因为不是一个全局组件,因此需要引入,而不需要 Vue.use()

模板

<template>
  <div id="app">
      <p class="title">演示图片来自:https://y.qq.com/</p>
      <p class="title">建议在移动端进行查看</p>
      <app-slider :list="sliders" :loop="true" v-if="sliders.length" width="100%" ></app-slider>
   </div>
</template>

js代码

import Slider from './components/Slider';
export default {
  name: 'app',
  data () {
    return {
      sliders:[
        {
          linkUrl:'http://y.qq.com/w/album.html?albummid=001RsOK33No4Sz&ADTAG=myqq&from=myqq&channel=10007100',
          picUrl:'https://y.gtimg.cn/music/photo_new/T003R720x288M000001EwfWs4QSIl6.jpg?max_age=2592000'
        },
        {
          linkUrl:'https://y.qq.com/msa/270/0_5102.html?ADTAG=myqq&from=myqq&channel=10007100',
          picUrl:'https://y.gtimg.cn/music/photo_new/T003R720x288M000001CV9tc1xK59w.jpg?max_age=2592000'
        },
        {
          linkUrl:'https://y.qq.com/m/client/music_headline/index.html?_hidehd=1&_button=2&zid=689728&ADTAG=myqq&from=myqq&channel=10007100',
          picUrl:'https://y.gtimg.cn/music/photo_new/T003R720x288M000002n0L9H3Bf8F0.jpg?max_age=2592000'
        },
        {
          linkUrl:'http://y.qq.com/w/album.html?albummid=001cTmxl1t64WK&ADTAG=myqq&from=myqq&channel=10007100',
          picUrl:'https://y.gtimg.cn/music/photo_new/T003R720x288M000001pf9Sy13Evav.jpg?max_age=2592000'
        },
        {
          linkUrl:'https://y.qq.com/msa/324/0_5116.html?ADTAG=myqq&from=myqq&channel=10007100',
          picUrl:'https://y.gtimg.cn/music/photo_new/T003R720x288M000002QkMgs2GfWgL.jpg?max_age=2592000'
        },
      ]
    }
  },
  components:{
    'app-slider':Slider
  }
}

六、效果:

1.jpg

七、预览:

gitosc pages 预览:

github:

gitosc: