最近在跟着视频补习Vue。仿QQ音乐上的轮播图。用了better-scroll
HTML部分,主要是三个部分。slider表示最外层容器,slider中包含slider-group和dots。slider-group表示轮播的具体内容和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" :class="{active:currentPageIndex===index}"></span>
</div>
</div>
</template>
js部分
1、设置组件的属性
props: {
/*可循环*/
loop: {
type: Boolean,
default: true
},
/*自动播放*/
autoPlay: {
type: Boolean,
default: true
},
/*间隔*/
interval: {
type: Number,
default: 4000
}
},
2、逻辑部分分为三步:设置轮播内容的宽度、初始化dots、初始化轮播内容、自动播放
分别将这四部封装给四个函数,在mounted中依次调用这四个函数。为了保证在dom已经完全生成了,采用setTimeout设置一个20ms的延时,因为浏览器刷新一次的速度在17ms左右。
mounted() {
setTimeout(() => {
this._setSliderWidth(); //设置宽度
this._initDots(); //初始化dots
this._initSlider(); //初始化轮播内容
if (this.autoPlay) { //自动播放
this._play();
}
}, 20);
…………
}
在methods中分别编写以上的四个函数
(1)_setSliderWidth----->设置每个轮播内容的宽度、设置轮播内容的父组件的宽度
_setSliderWidth(isResize) {
this.children = this.$refs.sliderGroup.children; //获取到slidergroup中的每个子元素
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"); //addClass是一个封装好为元素添加类名的函数,为字节点添加类名为slider-item 的类,进行CSS部分的渲染
child.style.width = sliderWidth + "px";//设置每个子节点的宽度
width += sliderWidth;//累加计算父元素的总宽度
}
//如果是循环播放并且是首次初始化时,需要再加上头尾两个重复图片的宽度。而当浏览器调整大小的时候,父元素中的子元素已经包含了首位重复图片的元素,因此不需要进行宽度的增加。
if (this.loop && !isResize) {
width += 2 * sliderWidth;
}
this.$refs.sliderGroup.style.width = width + "px"; //将总宽度赋值给父元素
},
(2)_initDots---->初始化dots的个数
_initDots() {
this.dots = new Array(this.children.length);
},
(3)_initSlider----->创建和配置一个better-scroll的实例、在bscroll的scrollEnd事件中进行当前index的计算、重置定时器从当前页开始自动播放
_initSlider() {
this.slider = new BScroll(this.$refs.slider, {//创建BScroll实例,并设置配置项
scrollX: true,
scrollY: false,
moentum: 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();//自动播放
}
});
},
(4)_play---->计算轮播的当前页、调用Bscrol的gotopage进行自动轮播
_play() {
let pageIndex = this.currentPageIndex + 1;
if (this.loop) {
pageIndex += 1;
}
this.timer = setTimeout(() => {
this.slider.goToPage(pageIndex, 0, 400);
}, this.interval);
}
3、额外代码
(1)调整窗口时,轮播图的宽度需要随着变化
window.addEventListener("resize", () => {
if (!this.slider) {
return;
}
this._setSliderWidth(true);
this.slider.refresh();
});
(2)addClass(为dom元素添加类的方法)
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)
}
(3)CSS代码(这块还没细看,之后看了需要补)
<style lang="stylus" scoped>
@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>
4、总结
整个轮播图的流程应该是,构建html框架,把图片填进去,这边由于填充的内容可以是图片或者其他内容,采用slot来编写组件;初始化dots;利用better-scroll使得轮播图可以动起来;利用dots的index和当前滚动到的页面index,来为特定的dot添加active样式;每次滚动完一次都触发自动轮播;利用gotopage来实现自动轮播。
这边有个坑就是由于slot中的内容是在父组件中初始化赋值的,可能会现在子组件已经初始化完毕但是父组件由于需要取数据没有初始化好的问题,会导致子组件在mounted中获取不到一开始slot中的带数据的值,因此可以在父组件中添加v-if,当获取到数据后再进行子组件的渲染。