基于vue音乐APP的项目笔记:轮播图组件封装(下)
我们现在开始对上次的代码进行封装,我们上次的代码是截至到我们通过jsonp已经获取到了轮播图的数据array,我们再定义一个轮播图组件即可
首先我们在base下建立一个组件我们将slider组件的模板定义了如下的样子
这个插槽就是起一个占位的作用
我们开始上手写代码,我们定义轮播图组件之前要做这几样事情,首先我们要定义一堆关于轮播图的数据
//定义轮播图的数据
props:{
loop:{
type:Boolean,
default:true
},
autoPlay:{
type:Boolean,
default:true
},
interval:{
type:Number,
default:4000
}
},
定义完成之后我们在钩子中创建几个方法
值得注意的是,由于项目经验,我们在加载这些方法的时候,要定义一个计时器,让他20毫秒进行加载,纯粹经验,不用多问
setTimeout(()=>{
//计算宽
this._setSliderWidth()
//计算点
this._initDots()
//初始化轮播图
this._initSlider()
//自动播放
if(this.autoPlay){
this._autoPlay()
}
},20)
首先我们先计算宽的方法
值得注意的是,我们在写轮播图组件的时候,要自动添加类名匹配到组件中的css样式,所以我们不仅要计算宽,还要动态添加类名
_setSliderWidth(resize){
//设置轮播图的宽
//获取子元素对象
this.children=this.$refs.sliderGroup.children
//设置宽度
let width=0;
//获取父级的宽度
let sliderWidth=this.$refs.slider.clientWidth;
for(let i=0;i<this.children.length;i++){
//给每个元素添加class类名
let childDom=this.children[i];
//调用公共dom方法
addClassName(childDom,"slider-item");
//设置宽度
childDom.style.width = sliderWidth + 'px';
//父级窗口随着递增
width+=sliderWidth;
}
//如果是loop循环,为了切换效果,需要2倍宽度
if(this.loop && !resize){
width += 2*sliderWidth;
}
this.$refs.sliderGroup.style.width=width + 'px'
},
这个方法的思路是,首先获取了父级窗口的宽度,然后通过循环子对象的个数,把父级的宽度设置给子元素,然后在循环的末尾,要自加一个width,等到循环结束之后,
这个width的值会是所有的子元素的宽总和,然后最后会把这个总和附给我们的视口总宽度。
值得注意的是我们这边添加class类名的方法,在common下添加了一个有关于dom操作的js封装。
这个封装主要是2部分,一个是添加class类名,一个是判断有没有class名字
/**
- 用于添加dom的公用js
*/
export function addClassName(el,className){
//如果有这个class
if(hasClassName(el,className)){
//什么都不做
return
}
let newClass = el.className.split(' ')
newClass.push(className)
el.className = newClass.join(' ')
}
//判断是否有class的方法
export function hasClassName(el,className){
//创建正则表达式
let reg=new RegExp('(^|\s)'+className+'(\s|$)');
return reg.test(el.className);
}
这个动态添加类名的思路是,把他们全部拆分成数组,空格隔开,因为可能实际代码中还有其他的类名。不能和他们冲突,所以push新的类名,
然后再把它join进字符串中,就完成了添加类名的操作
值得注意的是,如果我们有loop循环的数据的话我们需要×2
注意一下,需要在组件中自己定义css样式
.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%
这个时候我们就可以看一下效果,发现所有的img都是很合理的展示出来了
然后我们展示出来以后,需要能够左右滑动,我们这个时候就用到了better-scroll这个插件,所以我们需要安装它
然后在钩子中定义一个初始化轮播图的方法
//轮播图初始化
this.slider=new BScrool(this.$refs.slider,{
//传递参数配置
//横向滚动
scrollX:true,
scrollY:false,
//惯性
momentum:false,
snap:true,
snapLoop:this.loop,
snapThreshold:0.3,
snapSpeed:400,
click:true
})
然后就可以横向滚动无缝连接了
然后剩下的这个小点点,我们可以通过下面的方法实现,首先循环轮播图的个数来循环span标签
_initDots(){
this.dot=new Array(this.children.length);
},
这样就就可以循环长度了,这个时候就可以看到点了
循环之后,我们就开始可以做自动播放了
首先自动播放的原理就是,延时器+自增数字就可以做
首先我们定义一个data数字属性为0
_autoPlay(){
let pageIndex = this.pageIndex+1;
if(this.loop){
pageIndex += 1
}
this.timer = setTimeout(()=>{
this.slider.goToPage(pageIndex , 0 ,400)
},this.interval)
}
这边我们做了一个判断,如果是循环播放的话,那么就自加一
因为betterscroll在初始化的时候会增加多一页,要把第一页跳过。
然后定义一个定时器timer,使用了betterscroll中的一个方法,跳转页数,pageIndex是跳转到第几页,0指的是x轴,400指的是速度
我们跟初始化的速度参数保持一致都是400;
然后延时时间是定义的数据interval。
然后我们会发现在循环第一次的时候就不会有第二次了,这个是因为页面被挂载的时候autoplay方法只执行了一次。
解决方案是这样的:
在我们初始化轮播图后面写方法,因为在即将进入下一页的时候会派发一个事件,我们可以监听这个事件来做一些逻辑
this.slider.on('scrollEnd',()=>{
//pageX是横向滚动的索引
let ApageIndex = this.slider.getCurrentPage().pageX
if(this.loop){
//因为自动会给第一个拷贝到最后一个形成轮播图,所以这里要减一
ApageIndex -= 1
}
this.pageIndex=ApageIndex
if(this.autoPlay){
clearTimeout(this.timer)
this._autoPlay();
}
})
这里我们通过betterscroll的api来获取索引,然后把这个索引附给pageIndex,然后然后清除计时器,这个再重新执行自动播放的方法
为什么要清除计时器,因为如果在即将进入下一页的时候,我们人工手滑一下,就直接造成轮播图滚动异常,所以要每次清除。
现在我们的自动播放已经完成了,现在就改操作,这个点了,如果让他滚动到哪里,那个点就放大呢??
这个其实非常简单,我们只需要绑定一个class动态属性即可。
然后就大功告成。
但是还没有完毕,我们如果调整一下视口的宽度,会发现轮播图不会适应窗口了,那么该如何解决这个bug?
//监听视口宽度
window.addEventListener('resize',()=>{
//如果slider还没被初始化,就返回掉
if(!this.slider){
return
}
this._setSliderWidth(false)
})
通过监听视口宽度,来重新计算我们的宽度,那么为什么要传递一个false,那是因为我们计算宽度的时候由于是loop循环
要把宽×2,如果每次改变视口宽度,就一致要把宽×2,这样显然是不合适的,所以我们要通过一个参数来判断。
我们在宽度计算的方法中传递一个参数,如果这个参数不是false,那我们就可以×2
if(this.loop && !resize){
width += 2*sliderWidth;
}