Angular2利用组件与指令实现图片轮播组件


Posted in Javascript onMarch 27, 2017

前言

如果说模块系统是Angular2的灵魂,那其组件体系就是其躯体,在模块的支持下渲染出所有用户直接看得见的东西,一个项目最表层的东西就是组件呈现的视图。

而除了直接看的见的躯体之外,一个完整的“生物”还需要有感觉器官,用来感知外界与其的交互,这就是指令要做的事情。
本文将使用Angular2提供的强大的组件与指令等功能制作出一个简单的图片轮播控件,继续上文打的比方的话这就像是一个“器官”,功能是呈现图片,并感知用户的点击或手势来切换图片。

一、创建组件

结束上文打的尴尬的比方,着眼于一个待开发的ng2项目,它有一个空白的特性页面,现在需要在上面呈现一个图片轮播窗口。

图片轮播是一个需要给用户看见的东西,所以应该使用ng2的组件(Component)来实现它,并且这个功能较为通用,可以将其独立出来方便以后再次使用到。

所以在项目中的共享模块(SharedModule)下创建这个组件名为 slide-img.component

使用ng2提供的组建装饰器来将这个TypeScript模块正式变身成ng2的组件:

@Component({
 selector: 'my-slide-img',
 templateUrl: 'slide-img.component.html',
 styleUrls: ['slide-img.component.css'],
 animations: [
  trigger('imgMove', [
   /** 不显示 */
   state('off', style({'display': 'none', 'z-index': '0', 'transform': 'translateX(0)'})),
   /** 上一张图片 */
   state('prev', style({'z-index': '1',
   'transform': 'translateX(-100%)'})),
   /** 下一张图片 */
   state('next', style({'z-index': '2', 'transform': 'translateX(100%)'})),
   /** 当前图片 */
   state('on', style({'z-index': '3', 'transform': 'translateX(0)'})),
   transition('prev=>on', [
    animate('0.3s ease-in')
   ]),
   transition('next=>on', [
    animate('0.3s ease-in')
   ]),
   transition('on=>prev', [
    animate('0.3s ease-in')
   ]),
   transition('on=>next', [
    animate('0.3s ease-in')
   ])
  ])
 ]
})
export class SlideImgComponent { }

其参数其实已经不能再明确了:

selector就是其使用时的标签名,

templateUrl即组件关联的界面的模板,

styleUrls即仅在此组件内生效的样式表,

animations定义的是一套ng2动画规则。

讲讲最后的这个动画规则:

ng2的动画其实非常简单,步骤为1.定义触发器名,2.定义状态,3.定义切换样式,4.将此触发器应用到具体的标签中,状态作为触发器的值传入。

当ng2检测到动画状态的值更改了,就会套用定义的切换样式,用法思路还算比较明确(当然实际使用时会有一些尴尬的小问题)

二、实现组件

既然是轮播图片组件,要做的事情首先就得是传入轮播图片然后显示出来。

使用过ng1的朋友一定还记得其在定义指令(angular.directive)的时候是通过scope参数(或者link)来传入数据的,而ng2中使用的是Input装饰器,使用的方法如下:

@Input() public imgs: SlideImg[];

使用了此装饰器的变量imgs将可以在运行时接收其他组件传入的图片列表。使用方法如下:

<my-slide-img [imgs]="imgs"></my-slide-img>

关于这里的方括号“[]”,ng2其实提供了多种方式来进行组件模板中值的传入,其中这种变量名用方括号包起来的方法就是其中之一,代表是输入的值,而后面会见到的圆括号来包围的方式,是代表输出的值。

传入了数据后,下一步就是要如何来显示图片到界面上了,没错就是ng1中ng-for指令的升级版*ngFor,除了写法外表面上的差别不大。

关于轮播图片逻辑的具体实现逻辑,笔者使用的方式就是利用ng2动画的状态切换,设置一个当前图片索引值current,*ngFor渲染的图片将其索引与当前索引比较,如果是相邻的图片则设为'prev'状态与'next'状态,ng2会为其加上位置属性为-100%或者100%,如果是当前图片则设为'on'状态,ng2会将其的位置属性设为0,其余均设为'off'状态,ng2会直接将其隐藏,实现的逻辑很简单,考虑也不算周全,笔者就不继续解释献丑了。

最终的轮播图片组件及其模板文件代码如下:

<div class="imgs">
 <img src="{{img.Url}}" alt="" class="img"
 *ngFor="let img of imgs;let i=index"
 (mySwipe)="Change($event)"
 [@imgMove]="ImgState(i)">
</div>

<div class="btn" (click)="Prev()">Prev</div>
<div class="btn" (click)="Next()">Next</div>
.imgs{
 position: relative;width: 100%;height: 15em;
 overflow: hidden;
}
.img{
 position: absolute;
 width: 100%;
 height: 100%;
 background: pink;
 transition: 0.2s;
}
.btn{
 display: inline-block;
 padding: 1em 2em;font-size: 1em;border-radius: 0.5em;
 border: 1px solid #ddd;cursor: pointer;
 margin: 1em;background: #eee;box-shadow: 0.1em 0.1em 0.2em #aaa;
}
.btn:active{
 background: #eee;
 box-shadow: none;
}
import { Component, OnInit, Input,
 animate,
 style,
 transition,
 trigger,
 state,
 HostListener
} from '@angular/core';
import { SlideImg } from './slide-img.interface';

@Component({
 selector: 'my-slide-img',
 templateUrl: 'slide-img.component.html',
 styleUrls: ['slide-img.component.css'],
 animations: [
  trigger('imgMove', [
   /** 不显示 */
   state('off', style({'display': 'none', 'z-index': '0', 'transform': 'translateX(0)'})),
   /** 上一张图片 */
   state('prev', style({'z-index': '1',
   'transform': 'translateX(-100%)'})),
   /** 下一张图片 */
   state('next', style({'z-index': '2', 'transform': 'translateX(100%)'})),
   /** 当前图片 */
   state('on', style({'z-index': '3', 'transform': 'translateX(0)'})),
   transition('prev=>on', [
    animate('0.3s ease-in')
   ]),
   transition('next=>on', [
    animate('0.3s ease-in')
   ]),
   transition('on=>prev', [
    animate('0.3s ease-in')
   ]),
   transition('on=>next', [
    animate('0.3s ease-in')
   ])
  ])
 ]
})
export class SlideImgComponent {
 @Input() public imgs: SlideImg[];
 public current;
 constructor() {
  this.current = 0;
 }
 public ImgState(index) {
  if (this.imgs && this.imgs.length) {
   if (this.current === 0) {
    return index === 0 ? 'on' :
    index === 1 ? 'next' :
    index === this.imgs.length - 1 ? 'prev' :
    'off';
   } else if (this.current === this.imgs.length - 1) {
    return index === this.imgs.length - 1 ? 'on' :
    index === this.imgs.length - 2 ? 'prev' :
    index === 0 ? 'next' :
    'off';
   }
   switch (index - this.current) {
    case 0:
     return 'on';
    case 1:
     return 'next';
    case -1:
     return 'prev';
    default:
     return 'off';
   }
  } else {
   return 'off';
  }
 }
 public Next() {
  this.current = (this.current + 1) % this.imgs.length;
 }
 public Prev() {
  this.current = this.current - 1 < 0 ? this.imgs.length - 1 : this.current - 1;
 }

 public Change(e) {
  if (e === 'left') {
   this.Next();
  } else if (e === 'right') {
   this.Prev();
  }
 }
}

其中有两个地方为讲到,一个是组件代码引入了一个slide-img.interface 模块,这个仅仅用来规范一下轮播图片的格式,二是在html中还有一个节点名为(mySwipe)这就是接下来要讲的输出属性,目前知道的它的作用是,当用户滑动图片时,将触发此节点配置的回调方法,所做的事情就是判断发生了左滑事件还是右滑事件,分别触发上一张图或下一张图的切换。

三、给轮播图片控件加上手势效果

轮播图片在移动端很需要加上手势滑动的效果,所以接下来要给这个轮播组件加上一个指令,用于响应用户的滑动手势。代码如下:

import { Directive, Input, HostListener, Output, EventEmitter } from '@angular/core';

@Directive({ selector: '[mySwipe]' })
export class SwipeDirective {
 @Output() public mySwipe = new EventEmitter<string>();

 private touchStartX;
 private touchStartY;
 @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.mySwipe.emit('right');
   } else if (moveX < -50) {
    this.mySwipe.emit('left');
   }
  } else if (Math.abs(moveY) > Math.abs(moveX)) {
   /**
    * Y轴移动大于X轴 判定为纵向滑动
    */
   if (moveY > 50) {
    this.mySwipe.emit('down');
   } else if (moveY < -50) {
    this.mySwipe.emit('up');
   }
  }
  this.touchStartX = this.touchStartY = -1;
 }
}

指令的声明甚至简单过组建的声明,因为指令不需要依赖于某个视图模板,只需要有个指令名称就足够了。

需要关心的是指令中定义的输出属性:

@Output() public mySwipe = new EventEmitter<string>();

此属性接收了上文组件中的Change($event)回调方法,在此指令中,所做的事情就是监听组件的滑动,收到滑动事件后就触发这个回调,监听使用的是ng2的HostListener装饰器,用法显而易见了。

现在运行起项目来看看效果吧(比较懒就不截动图了):

Angular2利用组件与指令实现图片轮播组件

总结以及题外话:

本文主要使用了ng2几个比较基本的功能——输入属性(Input装饰器)、输出属性(Outut装饰器)、HostListener装饰器、几个系统指令(ngFor)、ng2动画实现了一个比较基本的图片轮播控件。

使用好ng2的组件以及指令能完成很多的事情,其需要学习的东西绝不仅限与本文提到的,包括其底层的渲染,也很值得去研究。

最后提一个尴尬的问题点:

其实最初写这个轮播图片的时候想过要加上拖动的,也就是图片会随手势的滑动实时更新位置。

但后来发现ng2的动画有个尴尬的地方,那就是一定会从初始状态按照定义好的转换效果变化到目标状态。实时滑动需要我在滑动过程中就改变图片的位置,这样的话在滑动结束需要切换图片时,图片居然强行回到了初始位置然后才开始转换动画,一时还想不到继续使用ng2动画来实现这种实时滑动的完美解决办法,实在是尴尬。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Javascript 相关文章推荐
js定义对象或数组直接量时各浏览器对多余逗号的处理(json)
Mar 05 Javascript
JQuery一种取同级值的方式(比如你在GridView中)
Mar 15 Javascript
解析Jquery取得iframe中元素的几种方法
Jul 04 Javascript
jQuery父级以及同级元素查找介绍
Sep 04 Javascript
jquery使用淘宝接口跨域查询手机号码归属地实例
Nov 28 Javascript
Javascript实现返回上一页面并刷新的小例子
Dec 11 Javascript
用box固定长宽实现图片自动轮播js代码
Jun 09 Javascript
jquery中show()、hide()和toggle()用法实例
Jan 15 Javascript
js继承实现方法详解
Dec 16 Javascript
Mongoose经常返回e11000 error的原因分析
Mar 29 Javascript
angularjs实现上拉加载和下拉刷新数据功能
Jun 12 Javascript
在vue里面设置全局变量或数据的方法
Mar 09 Javascript
Vue 过渡实现轮播图效果
Mar 27 #Javascript
AngularJS2中一种button切换效果的实现方法(二)
Mar 27 #Javascript
使用AngularJS2中的指令实现按钮的切换效果
Mar 27 #Javascript
详解VUE的状态控制与延时加载刷新
Mar 27 #Javascript
vue2.0实战之使用vue-cli搭建项目(2)
Mar 27 #Javascript
js实现三级联动效果(简单易懂)
Mar 27 #Javascript
JS数组去重(4种方法)
Mar 27 #Javascript
You might like
各种战术和打法的原创者
2020/03/04 星际争霸
DC四月将推出百页特刊漫画 纪念小丑诞生80周年
2020/04/09 欧美动漫
新版mysql+apache+php Linux安装指南
2006/10/09 PHP
php结合正则批量抓取网页中邮箱地址
2015/05/19 PHP
php三元运算符知识汇总
2015/07/02 PHP
PHP文件系统管理(实例讲解)
2017/09/19 PHP
PHP 出现 http500 错误的解决方法
2021/03/09 PHP
通过jQuery源码学习javascript(二)
2012/12/27 Javascript
js控制的回到页面顶端goTop的代码实现
2013/03/20 Javascript
jquery $.each() 使用小探
2013/08/23 Javascript
Javascript正则控制文本框只能输入整数或浮点数
2014/09/02 Javascript
javascript中的this详解
2014/12/08 Javascript
jQuery插件datepicker 日期连续选择
2015/06/12 Javascript
原生javascript实现解析XML文档与字符串
2016/03/01 Javascript
购物车前端开发(jQuery和bootstrap3)
2016/08/27 Javascript
Vue自定义指令封装节流函数的方法示例
2018/07/09 Javascript
JavaScript模板引擎应用场景及实现原理详解
2018/12/14 Javascript
从0到1构建vueSSR项目之路由的构建
2019/03/07 Javascript
JQuery animate动画应用示例
2019/05/14 jQuery
js实现随机div颜色位置 类似满天星效果
2019/10/24 Javascript
Python映射拆分操作符用法实例
2015/05/19 Python
web.py在SAE中的Session问题解决方法(使用mysql存储)
2015/06/24 Python
Python selenium 父子、兄弟、相邻节点定位方式详解
2016/09/15 Python
让Python更加充分的使用Sqlite3
2017/12/11 Python
python使用pandas实现数据分割实例代码
2018/01/25 Python
DataFrame中的object转换成float的方法
2018/04/10 Python
Python修改列表值问题解决方案
2020/03/06 Python
沙龙级头发造型工具:FOXYBAE
2018/07/01 全球购物
微软巴西官方网站:Microsoft Brasil
2019/09/26 全球购物
JVM是一个编译程序还是解释程序
2012/09/11 面试题
给实习单位的感谢信
2014/02/01 职场文书
煤矿安全生产标语
2014/06/06 职场文书
股东出资证明书范例
2014/10/04 职场文书
2014年大班保育员工作总结
2014/12/02 职场文书
材料员岗位职责范本
2015/04/11 职场文书
中学生运动会广播稿
2015/08/19 职场文书