基于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 相关文章推荐
获取非最后一列td值并将title设为该值的方法
Oct 30 Javascript
javascript对话框使用方法(警告框 javascript确认框 提示框)
Jan 07 Javascript
Jquery 获取对象的几种方式介绍
Jan 17 Javascript
JavaScript按位运算符的应用简析
Feb 04 Javascript
javascript判断是手机还是电脑访问网页的简单实例分享
Jun 03 Javascript
jQuery实现鼠标滑过链接控制图片的滑动展开与隐藏效果
Oct 28 Javascript
详解maxlength属性在textarea里奇怪的表现
Dec 27 Javascript
JavaScript中的各种操作符使用总结
May 26 Javascript
vue项目使用axios发送请求让ajax请求头部携带cookie的方法
Sep 26 Javascript
JS动态图片的实现方法完整示例
Jan 13 Javascript
JavaScript Image对象实现原理实例解析
Aug 26 Javascript
vue中element 的upload组件发送请求给后端操作
Sep 07 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无限分类(树形类)
2013/09/28 PHP
php实现微信公众号无限群发
2015/10/11 PHP
Zend Framework分页类用法详解
2016/03/22 PHP
PHP实现的网站目录扫描索引工具
2016/09/08 PHP
jQuery源码分析-03构造jQuery对象-工具函数
2011/11/14 Javascript
js函数调用常用方法详解
2012/12/03 Javascript
javascript 回到顶部效果的实现代码
2014/02/17 Javascript
jQuery中hover方法和toggle方法使用指南
2015/02/27 Javascript
AngularJS使用angular-formly进行表单验证
2015/12/27 Javascript
jQuery实现简单的图片查看器
2020/09/11 Javascript
Node.js实用代码段之获取Buffer对象字节长度
2016/03/17 Javascript
JavaScript基础——使用Canvas绘图
2016/11/02 Javascript
echarts学习笔记之图表自适应问题详解
2017/11/22 Javascript
详解webpack与SPA实践之开发环境搭建
2017/12/18 Javascript
Vue写一个简单的倒计时按钮功能
2018/04/20 Javascript
vue短信验证性能优化如何写入localstorage中
2018/04/25 Javascript
angular 未登录状态拦截路由跳转的方法
2018/10/09 Javascript
详解JSON和JSONP劫持以及解决方法
2019/03/08 Javascript
亲自动手实现vue日历控件
2019/06/26 Javascript
element中Steps步骤条和Tabs标签页关联的解决
2020/12/08 Javascript
[13:21]DOTA2国际邀请赛采访专栏:RSnake战队国士无双,Fnatic.Fly
2013/08/06 DOTA
python根据文件大小打log日志
2014/10/09 Python
使用Python下的XSLT API进行web开发的简单教程
2015/04/15 Python
Python实现读写sqlite3数据库并将统计数据写入Excel的方法示例
2017/08/07 Python
pandas groupby 分组取每组的前几行记录方法
2018/04/20 Python
PyCharm 配置远程python解释器和在本地修改服务器代码
2019/07/23 Python
浅析Python 字符编码与文件处理
2020/09/24 Python
HTML5 3D衣服摇摆动画特效
2016/03/17 HTML / CSS
Lookfantastic俄罗斯:欧洲在线化妆品零售商
2019/08/06 全球购物
Nobody Denim官网:购买高级女士牛仔裤
2021/03/15 全球购物
高中生的自我鉴定范文
2014/01/24 职场文书
退休教师欢送会主持词
2014/03/31 职场文书
男女朋友协议书
2014/04/23 职场文书
2015年综治维稳工作总结
2015/04/07 职场文书
详解TypeScript中的类型保护
2021/04/29 Javascript
VMware虚拟机安装 Windows Server 2022的详细图文教程
2022/09/23 Servers