轮播图属于基础组件,所以归纳到src/base/slide里面,新建slide.vue
一、html部分
<template>
<div class="slider" ref="slider">
<div class="slider-group" ref="sliderGroup">
<slot>
</slot>
</div>
<div class="dots">
<span class="dot" v-for="(item, i) in dots" :key="i" :class="{active : currIndex === i}"></span>
</div>
</div>
</template>
- .slider作为最外层包裹,里面有两个元素:一个是包含所有.slider-item的.slider-group、另一个是包含所有.dot的.dots
- 为了使该组件更灵活,组件并不限定滑动元素必须是img,而是提供了一个插槽slot,你可以放置所有你想放置的元素,比如div、p等
- 为.slider-group添加ref="sliderGroup"是为了通过
this.$refs.sliderGroup.children
获取所有轮播元素的dom,以给它们都添加"slider-item"类 - 为.slider添加ref="slider"是为了获取父容器.slider的宽度,这样才能设置每一个轮播元素.slider-item的宽度与父容器的相等,继而能计算出包裹它们的.slider-group的宽度(累加和)
二、js部分
js代码100余行,全部展开不利于浏览,所以先看整体结构:
<script type="text/ecmascript-6">
import BScroll from 'better-scroll'
import {addClass} from 'common/js/dom'
export default {
name: 'slider',
props: {...},
data () {...},
mounted () {...},
methods: {...},
destoryed () {...}
}
</script>
- 首先安装better-scroll:
cnpm i better-scroll -S
- 引入
better-scroll
以及专题【三】中dom.js里的addClass
方法 -
name: 'slider'
:意味着使用方法为<slider></slider>
props
props: {
loop: {
type: Boolean,
default: true
},
autoPlay: {
type: Boolean,
default: true
},
interval: {
type: Number,
default: 4000
}
},
-
loop
:是否循环播放?默认:是 -
autoPlay
:是否自动播放?默认:是 -
interval
:切换时间间隔,单位:毫秒,默认:4000
data
data () {
return {
dots: [],
currIndex: 0
}
},
-
dots
:轮播图组件上的小圆点,数组长度等于元素.slider-item的个数 -
currIndex
:表示当前播放的是第几张,通过匹配循环索引,给当前项的小圆点添加active类(变成长条圆角状)
mounted
mounted () {
setTimeout(() => {
this._setSliderWidth()
this._initDots()
this._initSlider()
if (this.autoPlay) {
this._play()
}
}, 20) // 浏览器刷新一般需要17毫秒
window.addEventListener('resize', () => {
if (!this.slider) {
return false
}
this._setSliderWidth(true)
this.slider.refresh()
})
},
- 由于浏览器刷新通常花费17毫秒,所以为了保证dom成功渲染,需要在mounted钩子中,设置一个20毫秒的延时,20毫秒之后,再执行:
-
_setSliderWidth()
:获取轮播图宽度 -
_initDots()
:初始化小圆点 -
_initSlider()
:初始化轮播图 -
_play()
:如果允许自动播放就执行播放
-
- 监听window的resize事件,发现窗口尺寸变化,就重新设置轮播图宽度为当前窗口宽度、调用better-scroll组件的refresh方法
methods
methods: {
_setSliderWidth (isResize) {...},
_initDots () {...},
_initSlider () {...},
_play () {...}
}
_setSliderWidth()
// 获取轮播图宽度
_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
}
// 如果是循环播放,一头一尾需要多放一个dom
if (this.loop && !isResize) {
width += 2 * sliderWidth
}
this.$refs.sliderGroup.style.width = width + 'px'
},
-
this.$refs.sliderGroup.children
获取所有轮播元素的dom,给它们添加"slider-item"类 -
this.$refs.slider.clientWidth
获取父容器的宽度sliderWidth(实际上是视窗宽度) - 设置.slider-item的宽度等于sliderWidth
- 设置.slider-group的宽度等于所有.slider-item宽度累加的和
_initDots()
// 初始化小圆点
_initDots () {
this.dots = new Array(this.children.length)
},
- new一个
this.children.length
长度的数组,我们只关心this.dots的长度,至于数组的每一项是空(empty)还是有值,都不影响小圆点渲染
_initSlider()
// 初始化轮播图
_initSlider () {
this.slider = new BScroll(this.$refs.slider, {
scrollX: true,
scrollY: true,
momentum: false, // 惯性
snap: true,
snapLoop: this.loop,
snapThreshold: 0.3,
snapSpeed: 400
})
this.slider.on('scrollEnd', () => {
let pageIndex = this.slider.getCurrentPage().pageX
if (this.loop) {
pageIndex -= 1
}
this.currIndex = pageIndex
if (this.autoPlay) {
clearTimeout(this.timer)
this._play()
}
})
},
-
new BScroll
:创建一个better-scroll的实例- 第一个参数是一个原生的 DOM 对象。如果传递的是一个字符串,better-scroll 内部会尝试调用 querySelector 去获取这个 DOM 对象,所以初始化代码也可以是这样:
let scroll = new BScroll('.wrapper')
- 第二个参数是一个对象,包含了一些配置,详见文档
- 第一个参数是一个原生的 DOM 对象。如果传递的是一个字符串,better-scroll 内部会尝试调用 querySelector 去获取这个 DOM 对象,所以初始化代码也可以是这样:
getCurrentPage().pageX
:在横轴方向上获取当前页面索引pageIndex
_play()
// 播放
_play () {
let index = this.currIndex + 1
if (this.loop) {
index += 1
}
this.timer = setTimeout(() => {
this.slider.goToPage(index, 0, 400)
}, this.interval)
}
- 该函数设置了一个计时器,在规定的时间内(this.interval)执行一次BScroll对象的
goToPage
方法- 参数一: x 横轴的页数
- 参数二: y 纵轴的页数
- 参数三: 动画执行的时间
destoryed
destoryed () {
clearTimeout(this.timer)
}
三、css部分
<style lang="stylus" rel="stylesheet/stylus">
// variable.styl传送门:https://wy310.cn/2020/01/11/vue-build-basic-style-structure/
@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>
- 通过
@import
引入外部公用css:variable.styl - .slider-item被设置了左浮动,从而使所有滑动元素横向排成一排
- .slider-group规定子元素不换行