基于angular实现模拟微信小程序swiper组件
这段时间的主业是完成一个家政类小程序,终于是过审核发布了。不得不说微信的这个小程序生态还是颇有想法的,抛开他现有的一些问题不说,其提供的组件系统乍一看还是蛮酷的。比如其提供的一个叫swiper的视图组件,就可以在写界面的时候省不少时间和代码,轮播图片跟可滑动列表都可以用。导致现在回来写angular项目时也想整一个这样的组件出来,本文就将使用angular的组件能力和服务能力完成这么一个比较通用,耦合度较低的swiper出来。
首先要选择使用的技术,要实现的是与界面打交道的东西,自然是实现成一个组件,最终要实现的效果是写下这样的代码就可以完成一个可以滑动的视图来:
视图1 视图2
然后要把最基本的组件定义写出来,显然这里要定义两个组件。第一个是父级组件,选择器名字就叫ytm-swipers,目前做的事情仅仅是做一个外壳定义基本样式,使用时的子标签都会插入在ng-content标签中。
@Component({ selector:'ytm-swipers', template:`
第二个就是子视图了,在父级组件下,每个子组件都会沾满父级组件,只有当前的子组件会显示,当切换视图时实际做的就是更改这些子组件的显示方式,说的最简单的话,这个子组件还是仅仅用来加一个子外壳,给外壳添加基本样式,实际的页面内容原封不动放在ng-content标签中。
@Component({ selector:'swiper', template:`=0" [ngClass]="{'active':swiper.displayList[0]===childId, 'prev':swiper.displayList[2]===childId,'next':swiper.displayList[1]===childId}">
下一步是要让这两个父子组件完成心灵的沟通,讲道理其实可以直接使用ElementRef强行取到DOM来操作,不过这里使用的是组件内服务。和普通的服务使用上没差别,不过其provider是声明在某个组件里的,所以此服务只有在此组件以及子组件中可以注入使用。
@Injectable() classSwiperService{ publicswiperList:number[]; publicdisplayList:number[];//0为当前1为下一个2为上一个 publiccurrent:number; privatechanging:boolean; constructor(){ this.changing=false; this.swiperList=[]; this.displayList=[]; this.current=0; } publicAdd(id:number){ this.swiperList.push(id); switch(this.swiperList.length){ case1: this.displayList[0]=id; return; case2: this.displayList[1]=id; return; default: this.displayList[2]=id; return; } } publicNext():Promise{ if(this.changing){ returnnewPromise ((resolve,reject)=>{ returnreject('onchanging'); }); } this.changing=true; letc=this.swiperList.indexOf(this.displayList[0]); letn=this.swiperList.indexOf(this.displayList[1]); letp=this.swiperList.indexOf(this.displayList[2]); p=c; c=n; n=(c+1)%this.swiperList.length; this.displayList[0]=this.swiperList[c]; this.displayList[2]=this.swiperList[p]; this.displayList[1]=-1; setTimeout(()=>{ this.displayList[1]=this.swiperList[n]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } publicPrev():Promise { if(this.changing){ returnnewPromise ((resolve,reject)=>{ returnreject('onchanging'); }); } this.changing=true; letc=this.swiperList.indexOf(this.displayList[0]); letn=this.swiperList.indexOf(this.displayList[1]); letp=this.swiperList.indexOf(this.displayList[2]); n=c; c=p; p=p-1<0?this.swiperList.length-1:p-1; this.displayList[0]=this.swiperList[c]; this.displayList[1]=this.swiperList[n]; this.displayList[2]=-1; setTimeout(()=>{ this.displayList[2]=this.swiperList[p]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } publicSkip(index:number):Promise { letc=this.swiperList.indexOf(this.displayList[0]); if(this.changing||c===index){ returnnewPromise ((resolve,reject)=>{ reject('onchangingornochange'); }); } this.changing=true; letn=(index+1)%this.swiperList.length; letp=index-1<0?this.swiperList.length-1:index-1; this.displayList[0]=this.swiperList[index]; if(index>c){ this.displayList[2]=this.swiperList[p]; this.displayList[1]=-1; setTimeout(()=>{ this.displayList[1]=this.swiperList[n]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); }else{ this.displayList[1]=this.swiperList[n]; this.displayList[2]=-1; setTimeout(()=>{ this.displayList[2]=this.swiperList[p]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } } }
用到的变量包括:changing变量保证同时只能进行一个切换,保证切换完成才能进行下一个切换;swiperList装填所有的视图的id,这个id在视图初始化的时候生成;displayList数组只会有三个成员,装填的依次是当前视图在swiperList中的索引,下一个视图的索引,上一个视图的索引;current变量用户指示当前显示的视图的id。实际视图中的显示的控制就是使用ngClass指令来根据displayList和视图id附加相应的类,当前视图会正好显示,前一视图会在左边刚好遮挡,后一视图会在右边刚好遮挡。
同时服务还要提供几个方法:Add用于添加制定id的视图,Next用于切换到下一个视图(左滑时调用),Prev用于切换到前一个视图(右滑时调用),再来一个Skip用于直接切换到指定id的视图。
在子视图中注入此服务,需要在子视图初始化时生成一个id并Add到视图列表中:
exportclassYTMSwiperViewComponent{
publicchildId:number;
constructor(@Optional()@Host()publicswiper:SwiperService){
this.childId=this.swip
@Injectable() classSwiperService{ publicswiperList:number[]; publicdisplayList:number[];//0为当前1为下一个2为上一个 publiccurrent:number; privatechanging:boolean; constructor(){ this.changing=false; this.swiperList=[]; this.displayList=[]; this.current=0; } publicAdd(id:number){ this.swiperList.push(id); switch(this.swiperList.length){ case1: this.displayList[0]=id; return; case2: this.displayList[1]=id; return; default: this.displayList[2]=id; return; } } publicNext():Promise{ if(this.changing){ returnnewPromise ((resolve,reject)=>{ returnreject('onchanging'); }); } this.changing=true; letc=this.swiperList.indexOf(this.displayList[0]); letn=this.swiperList.indexOf(this.displayList[1]); letp=this.swiperList.indexOf(this.displayList[2]); p=c; c=n; n=(c+1)%this.swiperList.length; this.displayList[0]=this.swiperList[c]; this.displayList[2]=this.swiperList[p]; this.displayList[1]=-1; setTimeout(()=>{ this.displayList[1]=this.swiperList[n]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } publicPrev():Promise { if(this.changing){ returnnewPromise ((resolve,reject)=>{ returnreject('onchanging'); }); } this.changing=true; letc=this.swiperList.indexOf(this.displayList[0]); letn=this.swiperList.indexOf(this.displayList[1]); letp=this.swiperList.indexOf(this.displayList[2]); n=c; c=p; p=p-1<0?this.swiperList.length-1:p-1; this.displayList[0]=this.swiperList[c]; this.displayList[1]=this.swiperList[n]; this.displayList[2]=-1; setTimeout(()=>{ this.displayList[2]=this.swiperList[p]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } publicSkip(index:number):Promise { letc=this.swiperList.indexOf(this.displayList[0]); if(this.changing||c===index){ returnnewPromise ((resolve,reject)=>{ reject('onchangingornochange'); }); } this.changing=true; letn=(index+1)%this.swiperList.length; letp=index-1<0?this.swiperList.length-1:index-1; this.displayList[0]=this.swiperList[index]; if(index>c){ this.displayList[2]=this.swiperList[p]; this.displayList[1]=-1; setTimeout(()=>{ this.displayList[1]=this.swiperList[n]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); }else{ this.displayList[1]=this.swiperList[n]; this.displayList[2]=-1; setTimeout(()=>{ this.displayList[2]=this.swiperList[p]; this.changing=false; },500); returnnewPromise ((resolve,reject)=>{ returnresolve(this.displayList[0]); }); } } } er.swiperList.length;
this.swiper.Add(this.swiper.swiperList.length);
}
}
这个id其实就是已有列表的索引累加,且一旦有新视图被初始化,都会添加到列表中(支持动态加入很酷,虽然不知道会有什么隐藏问题发生)。
父组件中首先必须要配置一个provider声明服务:
@Component({ selector:'ytm-swipers', template:`