基于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 相关文章推荐
jquery图片放大镜功能的实例代码
Mar 26 Javascript
Jquery右下角抖动、浮动 实例代码(兼容ie6、FF)
Aug 15 Javascript
node.js中的buffer.toJSON方法使用说明
Dec 14 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
Dec 01 Javascript
详解Bootstrap插件
Apr 25 Javascript
JavaScript动态添加css样式和script标签
Jul 19 Javascript
表单input项使用label同时引用Bootstrap库导致input点击效果区增大问题
Oct 11 Javascript
微信小程序 教程之模板
Oct 18 Javascript
js实现交通灯效果
Jan 13 Javascript
Vue.js基础学习之class与样式绑定
Mar 20 Javascript
angular.js指令中的controller、compile与link函数的不同之处
May 10 Javascript
JavaScript实现简单的双色球(实例讲解)
Jul 31 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
PHP显示今天、今月、上月、今年的起点/终点时间戳的代码
2011/05/25 PHP
php列出一个目录下的所有文件的代码
2012/10/09 PHP
PHP合并静态文件详解
2014/11/14 PHP
Laravel与CI框架中截取字符串函数
2016/05/08 PHP
[原创]smarty简单模板变量输出方法
2016/07/09 PHP
学习YUI.Ext 第四天--对话框Dialog的使用
2007/03/10 Javascript
JS 建立对象的方法
2007/04/21 Javascript
js 设置选中行的样式的实现代码
2010/05/24 Javascript
一个简单的Ext.XTemplate的实例代码
2012/03/18 Javascript
javascript之典型高阶函数应用介绍二
2013/01/10 Javascript
Extjs4 关于Store的一些操作(加载/回调/添加)
2013/04/18 Javascript
写JQuery插件的基本知识
2013/11/25 Javascript
JavaScript中的acos()方法使用详解
2015/06/14 Javascript
10个很棒的jQuery代码片段
2015/09/24 Javascript
详解webpack中的hash、chunkhash、contenthash区别
2018/01/05 Javascript
从vue源码解析Vue.set()和this.$set()
2018/08/30 Javascript
原生js实现抽奖小游戏
2019/06/27 Javascript
JavaScript文档加载模式以及元素获取
2020/07/28 Javascript
Python基础入门之seed()方法的使用
2015/05/15 Python
使用Python机器学习降低静态日志噪声
2018/09/29 Python
pandas 将索引值相加的方法
2018/11/15 Python
10招!看骨灰级Pythoner玩转Python的方法
2019/04/15 Python
关于pytorch处理类别不平衡的问题
2019/12/31 Python
Python +Selenium解决图片验证码登录或注册问题(推荐)
2020/02/09 Python
Python tornado上传文件的功能
2020/03/26 Python
python数据类型强制转换实例详解
2020/06/22 Python
HTML5利用约束验证API来检查表单的输入数据的代码实例
2016/12/20 HTML / CSS
乌克兰香水和化妆品网站:Notino.ua
2018/03/26 全球购物
英国二手iPhone、音乐、电影和游戏商店:musicMagpie
2018/10/26 全球购物
给幼儿园老师的表扬信
2014/01/19 职场文书
金融管理专业毕业生求职信
2014/03/12 职场文书
婚前协议书范本两则
2014/10/16 职场文书
保研导师推荐信
2015/03/25 职场文书
2015年团支部工作总结
2015/04/03 职场文书
如何拟写通知正文?
2019/04/02 职场文书
《初涉尘世》读后感3篇
2020/01/10 职场文书