跟着做一个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>