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