基于angular实现模拟微信小程序swiper组件


Posted in Javascript onJune 11, 2017

这段时间的主业是完成一个家政类小程序,终于是过审核发布了。不得不说微信的这个小程序生态还是颇有想法的,抛开他现有的一些问题不说,其提供的组件系统乍一看还是蛮酷的。比如其提供的一个叫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.swip

@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]);
      });
    }
  }
}
er.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的那些组件就厉害深奥多了,笔者还有很长的路要走。

以上所述是小编给大家介绍的基于angular实现模拟微信小程序swiper组件,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言小编会及时回复大家的!

Javascript 相关文章推荐
静态图片的十一种滤镜效果--不支持Ie7及非IE浏览器。
Mar 06 Javascript
jQuery学习2 选择器的使用说明
Feb 07 Javascript
JSONP 跨域访问代理API-yahooapis实现代码
Dec 02 Javascript
利用Javascript判断操作系统的类型实现不同操作系统下的兼容性
Jan 29 Javascript
JS实现鼠标经过好友列表中的好友头像时显示资料卡的效果
Jul 02 Javascript
js实现超酷的照片墙展示效果图附源码下载
Oct 08 Javascript
JS &amp; JQuery 动态添加 select option
Jun 08 Javascript
vue.js选中动态绑定的radio的指定项
Jun 02 Javascript
js实现图片轮播效果学习笔记
Jul 26 Javascript
Node.js使用Express.Router的方法
Nov 14 Javascript
JS简单判断是否在微信浏览器打开的方法示例
Jan 08 Javascript
Vue中component标签解决项目组件化操作
Sep 04 Javascript
JavaScrpt判断一个数是否是质数的实例代码
Jun 11 #Javascript
JavaScript切换搜索引擎的导航网页搜索框实例代码
Jun 11 #Javascript
jQuery复合事件结合toggle()方法的用法示例
Jun 10 #jQuery
jQuery复合事件用法示例
Jun 10 #jQuery
jQuery简单绑定单个事件的方法示例
Jun 10 #jQuery
jQuery正则验证注册页面经典实例
Jun 10 #jQuery
详解Angular4中路由Router类的跳转navigate
Jun 09 #Javascript
You might like
根德Grundig S400/S500/S700电路分析
2021/03/02 无线电
解析php中var_dump,var_export,print_r三个函数的区别
2013/06/21 PHP
实现WordPress主题侧边栏切换功能的PHP脚本详解
2015/12/14 PHP
详解PHP序列化和反序列化原理
2018/01/15 PHP
关于php开启错误提示的总结
2019/09/24 PHP
PHP正则表达式函数preg_replace用法实例分析
2020/06/04 PHP
CCPry JS类库 代码
2009/10/30 Javascript
统计jQuery中各字符串出现次数的工具
2012/05/03 Javascript
javascript 判断中文字符长度的函数代码
2012/08/27 Javascript
jquery的trigger和triggerHandler的区别示例介绍
2014/04/20 Javascript
JS实现页面数据无限加载
2016/09/13 Javascript
jQuery图片切换动画效果
2017/02/28 Javascript
jQuery插件HighCharts绘制的基本折线图效果示例【附demo源码下载】
2017/03/07 Javascript
微信小程序开发图片拖拽实例详解
2017/05/05 Javascript
自定义事件解决重复请求BUG的问题
2017/07/11 Javascript
解决修复npm安装全局模块权限的问题
2018/05/17 Javascript
微信小程序wx.uploadfile 本地文件转base64的实现代码
2018/06/28 Javascript
JavaScript对象的浅拷贝与深拷贝实例分析
2018/07/25 Javascript
vue 根据数组中某一项的值进行排序的方法
2018/08/30 Javascript
Jquery遍历筛选数组的几种方法和遍历解析json对象,Map()方法详解以及数组中查询某值是否存在
2019/01/18 jQuery
vue router 跳转时打开新页面的示例方法
2019/07/28 Javascript
Vue 实现从小到大的横向滑动效果详解
2019/10/16 Javascript
[04:36]DOTA2国际邀请赛 ti3精彩集锦
2013/08/19 DOTA
Windows系统下安装Python的SSH模块教程
2015/02/05 Python
linux环境下python中MySQLdb模块的安装方法
2017/06/16 Python
python查看文件大小和文件夹内容的方法
2019/07/08 Python
python实现定时发送邮件到指定邮箱
2020/12/23 Python
采购部岗位职责
2013/11/24 职场文书
外贸业务员工作职责
2014/01/06 职场文书
优秀信贷员先进事迹
2014/01/31 职场文书
团支部推优材料
2014/05/21 职场文书
重阳节演讲稿:尊敬帮助老人 弘扬传统美德
2014/09/25 职场文书
电力工程合作意向书
2015/05/11 职场文书
时尚女魔头观后感
2015/06/04 职场文书
2016年教师党员公开承诺书
2016/03/24 职场文书
maven依赖的version声明控制方式
2022/01/18 Java/Android