基于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 相关文章推荐
多种方式实现JS调用后台方法进行数据交互
Aug 20 Javascript
JQuery对id中含有特殊字符的转义处理示例
Sep 06 Javascript
window.location.href中url中数据量太大时的解决方法
Dec 23 Javascript
动态显示可输入的字数提示还可以输入的字数
Apr 01 Javascript
Jquery实现仿京东商城省市联动菜单
Nov 19 Javascript
jquery实现表单验证简单实例演示
Nov 23 Javascript
分享12个实用的jQuery代码片段
Mar 09 Javascript
详解angularJs模块ui-router之状态嵌套和视图嵌套
Apr 28 Javascript
关于使用axios的一些心得技巧分享
Jul 02 Javascript
JsChart组件使用详解
Mar 04 Javascript
layui之select的option叠加问题的解决方法
Mar 08 Javascript
详解使用mpvue开发github小程序总结
Jul 25 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
杏林同学录(三)
2006/10/09 PHP
纯PHP生成的一个树叶图片画图例子
2014/04/16 PHP
thinkphp实现发送邮件密码找回功能实例
2014/12/01 PHP
PHP利用APC模块实现文件上传进度条的方法
2015/01/26 PHP
Yii2使用自带的UploadedFile实现的文件上传
2016/06/20 PHP
JavaScript 模拟用户单击事件
2009/12/31 Javascript
不要在cookie中使用特殊字符的原因分析
2010/07/13 Javascript
FireFox下XML对象转化成字符串的解决方法
2011/12/09 Javascript
node.js解决获取图片真实文件类型的问题
2014/12/20 Javascript
javascript中Number对象的toString()方法分析
2014/12/20 Javascript
asp.net+js实现金额格式化
2015/02/27 Javascript
jQuery on()方法使用技巧详解
2015/04/16 Javascript
JS+JSP通过img标签调用实现静态页面访问次数统计的方法
2015/12/14 Javascript
Backbone.js框架中Model与Collection的使用实例
2016/05/07 Javascript
Node.js实现兼容IE789的文件上传进度条
2016/09/02 Javascript
BootStrap的两种模态框方式
2017/05/10 Javascript
Vue+Jwt+SpringBoot+Ldap完成登录认证的示例代码
2018/05/21 Javascript
python快速查找算法应用实例
2014/09/26 Python
在Linux系统上通过uWSGI配置Nginx+Python环境的教程
2015/12/25 Python
Python简单连接MongoDB数据库的方法
2016/03/15 Python
Python简单实现的代理服务器端口映射功能示例
2018/04/08 Python
django使用haystack调用Elasticsearch实现索引搜索
2019/07/24 Python
python之生产者消费者模型实现详解
2019/07/27 Python
pandas的排序和排名的具体使用
2019/07/31 Python
python 模拟银行转账功能过程详解
2019/08/06 Python
python 代码实现k-means聚类分析的思路(不使用现成聚类库)
2020/06/01 Python
html5本地存储_动力节点Java学院整理
2017/07/12 HTML / CSS
工作中的自我评价如何写好
2013/10/28 职场文书
颁奖晚会主持词
2014/03/25 职场文书
实习指导老师评语
2014/04/26 职场文书
促销活动总结范文
2014/04/30 职场文书
团结就是力量演讲稿
2014/05/21 职场文书
优秀团员事迹材料1500字
2014/08/31 职场文书
工作失误检讨书
2015/01/26 职场文书
工作收入证明范本
2015/06/12 职场文书
解决jupyter notebook图片显示模糊和保存清晰图片的操作
2021/04/24 Python