这段时间的主业是完成一个家政类小程序,终于是过审核发布了。不得不说微信的这个小程序生态还是颇有想法的,抛开他现有的一些问题不说,其提供的组件系统乍一看还是蛮酷的。比如其提供的一个叫swiper的视图组件,就可以在写界面的时候省不少时间和代码,轮播图片跟可滑动列表都可以用。导致现在回来写angular项目时也想整一个这样的组件出来,本文就将使用angular的组件能力和服务能力完成这么一个比较通用,耦合度较低的swiper出来。

首先要选择使用的技术,要实现的是与界面打交道的东西,自然是实现成一个组件,最终要实现的效果是写下这样的代码就可以完成一个可以滑动的视图来:

<swipers>

<swiper>视图1</swiper>

<swiper>视图2</swiper>

</swipers>

然后要把最基本的组件定义写出来,显然这里要定义两个组件。第一个是父级组件,选择器名字就叫ytm-swipers,目前做的事情仅仅是做一个外壳定义基本样式,使用时的子标签都会插入在ng-content标签中。

 @Component({
selector: 'ytm-swipers',
template: `
<div class="view-body">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-body{height: 100%;width: 100%;overflow: hidden;position: relative;}
`]
})

第二个就是子视图了,在父级组件下,每个子组件都会沾满父级组件,只有当前的子组件会显示,当切换视图时实际做的就是更改这些子组件的显示方式,说的最简单的话,这个子组件还是仅仅用来加一个子外壳,给外壳添加基本样式,实际的页面内容原封不动放在ng-content标签中。

 @Component({
selector: 'swiper',
template: `
<div class="view-child" *ngIf="swiper.displayList.indexOf(childId) >= 0"
[ngClass]="{'active': swiper.displayList[0] === childId,
'prev': swiper.displayList[2] === childId, 'next': swiper.displayList[1] === childId}">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-child{
height: 100%;width: 100%;position: absolute;top: 0;
transition: 0.5s linear;background: #fff;
overflow-x: hidden;
}
.view-child.active{left: 0;z-index: 9;}
.view-child.next{left: 100%;z-index: 7;}
.view-child.prev{left: -100%;z-index: 8;}
`]
})

下一步是要让这两个父子组件完成心灵的沟通,讲道理其实可以直接使用ElementRef强行取到DOM来操作,不过这里使用的是组件内服务。和普通的服务使用上没差别,不过其provider是声明在某个组件里的,所以此服务只有在此组件以及子组件中可以注入使用。

 @Injectable()
class SwiperService {
public swiperList: number[];
public displayList: number[]; // 0为当前 1为下一个 2为上一个
public current: number;
private changing: boolean;
constructor() {
this.changing = false;
this.swiperList = [];
this.displayList = [];
this.current = 0;
}
public Add(id: number) {
this.swiperList.push(id);
switch (this.swiperList.length) {
case 1:
this.displayList[0] = id;
return;
case 2:
this.displayList[1] = id;
return;
default:
this.displayList[2] = id;
return;
}
}
public Next(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = 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);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Prev(): Promise<any> {
if (this.changing) {
return new Promise<any>((resolve, reject) => {
return reject('on changing');
});
}
this.changing = true;
let c = this.swiperList.indexOf(this.displayList[0]);
let n = this.swiperList.indexOf(this.displayList[1]);
let p = 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);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
public Skip(index: number): Promise<any> {
let c = this.swiperList.indexOf(this.displayList[0]);
if (this.changing || c === index) {
return new Promise<any>((resolve, reject) => {
reject('on changing or no change');
});
}
this.changing = true;
let n = (index + 1) % this.swiperList.length;
let p = 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);
return new Promise<any>((resolve, reject) => {
return resolve(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);
return new Promise<any>((resolve, reject) => {
return resolve(this.displayList[0]);
});
}
}
}

组件内服务

用到的变量包括: changing变量保证同时只能进行一个切换,保证切换完成才能进行下一个切换;swiperList装填所有的视图的id,这个id在视图初始化的时候生成;displayList数组只会有三个成员,装填的依次是当前视图在swiperList中的索引,下一个视图的索引,上一个视图的索引;current变量用户指示当前显示的视图的id。实际视图中的显示的控制就是使用ngClass指令来根据displayList和视图id附加相应的类,当前视图会正好显示,前一视图会在左边刚好遮挡,后一视图会在右边刚好遮挡。

同时服务还要提供几个方法:Add用于添加制定id的视图,Next用于切换到下一个视图(左滑时调用),Prev用于切换到前一个视图(右滑时调用),再来一个Skip用于直接切换到指定id的视图。

在子视图中注入此服务,需要在子视图初始化时生成一个id并Add到视图列表中:

 export class YTMSwiperViewComponent {
public childId: number;
constructor(@Optional() @Host() public swiper: SwiperService) {
this.childId = this.swiper.swiperList.length;
this.swiper.Add(this.swiper.swiperList.length);
}
}

这个id其实就是已有列表的索引累加,且一旦有新视图被初始化,都会添加到列表中(支持动态加入很酷,虽然不知道会有什么隐藏问题发生)。

父组件中首先必须要配置一个provider声明服务:

 @Component({
selector: 'ytm-swipers',
template: `
<div class="view-body">
<ng-content></ng-content>
</div>
`,
styles: [`
.view-body{height: 100%;width: 100%;overflow: hidden;position: relative;}
`],
providers: [SwiperService]
})

然后就是要监听手势滑动事件,做出相应的切换。以及传入一个current变量,每当此变量更新时都要切换到对应id的视图去,实际使用效果就是:

<ytm-swipers [current]="1">...</ytm-swipers>可以将视图切换到id为1的视图也就是第二个视图。

 export class YTMSwiperComponent implements OnChanges {
@Input() public current: number;
@Output() public onSwiped = new EventEmitter<Object>();
private touchStartX;
private touchStartY;
constructor(private swiper: SwiperService) {
this.current = 0;
}
public ngOnChanges(sc: SimpleChanges) {
if (sc.current && sc.current.previousValue !== undefined &&
sc.current.previousValue !== sc.current.currentValue) {
this.swiper.Skip(sc.current.currentValue).then((id) => {
console.log(id);
this.onSwiped.emit({current: id, bySwipe: false});
}).catch((err) => {
console.log(err);
});
}
}
@HostListener('touchstart', ['$event']) public onTouchStart(e) {
this.touchStartX = e.changedTouches[0].clientX;
this.touchStartY = e.changedTouches[0].clientY;
}
@HostListener('touchend', ['$event']) public onTouchEnd(e) {
let moveX = e.changedTouches[0].clientX - this.touchStartX;
let moveY = e.changedTouches[0].clientY - this.touchStartY;
if (Math.abs(moveY) < Math.abs(moveX)) {
/**
* Y轴移动小于X轴 判定为横向滑动
*/
if (moveX > 50) {
this.swiper.Prev().then((id) => {
// this.current = id;
this.onSwiped.emit({current: id, bySwipe: true});
}).catch((err) => {
console.log(err);
});
} else if (moveX < -50) {
this.swiper.Next().then((id) => {
// this.current = id;
this.onSwiped.emit({current: id, bySwipe: true});
}).catch((err) => {
console.log(err);
});
}
}
this.touchStartX = this.touchStartY = -1;
}
}

父组件实现

此外代码中还添加了一个回调函数,可以再视图完成切换时执行传入的回调,这个使用的是angular的EventEmitter能力。

以上就是全部实现了,实际的使用示例像这样:

 <ytm-swipers [current]="0" (onSwiped)="切换回调($event)">
<swiper>
视图1
</swiper>
<swiper>
视图2
</swiper>
<swiper>
视图3
</swiper>
</ytm-swipers>

视图的切换有了两种方式,一是手势滑动,不过没有写实时拖动,仅仅是判断左右滑做出反应罢了,二是更新[current]节点的值。

整个组件的实现没有使用到angular一些比较底层的能力,仅仅是玩弄css样式以及组件嵌套并通过服务交互,以及Input、Output与外界交互。相比之下ionic的那些组件就厉害深奥多了,笔者还有很长的路要走。

很久未写博客,原因之一是临毕业事情有点多,赚了波奖学金不亏,之二是为了写游戏一边在玩游戏一边在学DX编程,之三是懒。

基于angular实现模拟微信小程序swiper组件的更多相关文章

  1. 微信小程序 swiper 组件坑

    swiper 组件高度被限制为150px了,所以内容无法撑开. 解决办法 给这组件重新设置个高度,然后在把里面的图片设置为自动适应容器大小.图片模式设置为 宽度不变 自动适应高度 <swiper ...

  2. 第五篇、微信小程序-swiper组件

    常用属性: 效果图: swiper.wxml添加代码: <swiper indicator-dots="{{indicatorDots}}" autoplay="{ ...

  3. 微信小程序--swiper组件

    <view class='swiper-container'> <swiper indicator-dots="true" autoplay=" ver ...

  4. 微信小程序swiper组件实现图片宽度自适应

    wxml 代码: <!--pages/swipe/swipe.wxml--> <view> <swiper circular="true" indic ...

  5. 基于Shiro,JWT实现微信小程序登录完整例子

    小程序官方流程图如下,官方地址 : https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html ...

  6. 微信小程序swiper高度自适应,swiper的子元素高度不固定

    小程序 swiper 组件默认高度150px,并且如果子元素过高,swiper不会自适应高度 解决方案一: (总体来说不够完美,适合满屏滑动) 如果不是满屏的状态,用scroll-view IOS滑动 ...

  7. 微信小程序_(组件)swiper轮播图

    微信小程序swiper轮播图组件官方文档 传送门 Learn: swiper组件 一.swiper组件 indicator-dots:是否显示面板指示点[默认值false] autoplay:是否自动 ...

  8. 微信小程序swiper实现 句子控app首页滑动卡片

    微信小程序swiper实现 句子控app首页滑动卡片 引言:最近看到句子控APP首页的效果很清新,可是发现他的微信小程序端没有实现这个功能,我看了一下难度不大,于是尝试着去实现. 实现效果如下: 1. ...

  9. 5个最优秀的微信小程序UI组件库

    开发微信小程序的过程中,选择一款好用的组件库,可以达到事半功倍的效果.自从微信小程序面世以来,不断有一些开源组件库出来,下面5款就是排名比较靠前,用户使用量与关注度比较高的小程序UI组件库.还没用到它 ...

随机推荐

  1. php-fpm死机解决办法,脚本后台自动重启

    本人用nginx+php7搭建了一台服务器,因为请求量太大,而且php里面又有挂起的任务,导致php-fpm在高峰期的时候经常死掉,吧php-fpm的最大进程数已经改到1000了,还是吃不消,cpu也 ...

  2. Java scheduled executor

    A typical usage of java scheduled executor looks like this ScheduledExecutorService executor = Execu ...

  3. 【CSS】思考和再学习——关于CSS中浮动和定位对元素宽度/外边距/其他元素所占空间的影响

      一.width:auto和width:100%的区别   1.width:100%的作用是占满它的参考元素的宽度.(一般情况下参考元素 == 父级元素,这里写成参考元素而不是父级元素,在下面我会再 ...

  4. arcgis属性选取like用法

    查询对象为ArcInfo coverage,shapefile, INFO table,dBASE table,ArcSDE data,ArcIMS 要素类,或者 ArcIMS image servi ...

  5. JavaScript异步编程

    前言 如果你有志于成为一个优秀的前端工程师,或是想要深入学习JavaScript,异步编程是必不可少的一个知识点,这也是区分初级,中级或高级前端的依据之一.如果你对异步编程没有太清晰的概念,那么我建议 ...

  6. Thinkphp与CI的区别

    深入学习一门新技术的最好方法就是看官方文档. ThinkPHP5.0文档: http://www.kancloud.cn/manual/thinkphp5/118003 官方的说辞是: 主要特性 : ...

  7. C# Redis学习系列三:Redis配置主从

    Redis配置主从 主IP :端口      192.168.0.103 6666 从IP:端口       192.168.0.108 3333 配置从库 (1)安装服务: redis-server ...

  8. bash Shell条件测试

    3种测试命令: test EXPRESSION [ EXPRESSION ] [[ EXPRESSION ]]  注意:EXPRESSION前后必须有空白字符 bash的测试类型 数值测试: -eq: ...

  9. hdu1312 Red and Black 简单BFS

    简单BFS模版题 不多说了..... 直接晒代码哦.... #include<cstdlib> #include<iostream> #include<cstdio> ...

  10. makefile文件模板介绍

    1    src : = $(shell  ls  *.c)2    objs : = $(patsubst  %.c, %.o, $(src))3    test : $(objs)4       ...