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 相关文章推荐
javascript网页关键字高亮代码
Jul 30 Javascript
比较全面的event对像在IE与FF中的区别 推荐
Sep 21 Javascript
javascript 获取iframe里页面中元素值的方法
Feb 17 Javascript
Javascript字符串浏览器兼容问题分析
Dec 01 Javascript
JS实现密码框根据焦点的获取与失去控制文字的消失与显示效果
Nov 26 Javascript
深入理解MVC中的时间js格式化
May 19 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
Jul 21 Javascript
微信小程序  网络请求API详解
Oct 25 Javascript
Vue.js鼠标悬浮更换图片功能
May 17 Javascript
关于JavaScript中高阶函数的魅力详解
Sep 07 Javascript
详解如何制作并发布一个vue的组件的npm包
Nov 10 Javascript
ES5 模拟 ES6 的 Symbol 实现私有成员功能示例
May 06 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
PHP中header用法小结
2016/05/23 PHP
php实现遍历文件夹的方法汇总
2017/03/02 PHP
php 判断页面或图片是否经过gzip压缩的方法
2017/04/05 PHP
PHP实现从上往下打印二叉树的方法
2018/01/18 PHP
浅析PHP类的反射来实现依赖注入过程
2018/02/06 PHP
二行代码解决全部网页木马
2008/03/28 Javascript
jcarousellite.js 基于Jquery的图片无缝滚动插件
2010/12/30 Javascript
鼠标放在图片上显示大图的JS代码
2013/03/26 Javascript
JavaScript计划任务后台运行的方法
2015/12/18 Javascript
浅析Javascript匿名函数与自执行函数
2016/02/06 Javascript
JS打印组合功能
2016/08/04 Javascript
Restify中接入Socket.io报Error:Can’t set headers的错误解决
2017/03/28 Javascript
详解ES6之async+await 同步/异步方案
2017/09/19 Javascript
Vue的移动端多图上传插件vue-easy-uploader的示例代码
2017/11/27 Javascript
js 数组详细操作方法及解析合集
2018/06/01 Javascript
vue实现鼠标移入移出事件代码实例
2019/03/27 Javascript
微信小程序 slot踩坑的解决
2019/04/01 Javascript
[58:21]DOTA2亚洲邀请赛 4.3 突围赛 Liquid vs VGJ.T 第二场
2018/04/04 DOTA
python 远程统计文件代码分享
2015/05/14 Python
python通过ssh-powershell监控windows的方法
2015/06/02 Python
横向对比分析Python解析XML的四种方式
2016/03/30 Python
python3读取csv和xlsx文件的实例
2018/06/22 Python
解决pyCharm中 module 调用失败的问题
2020/02/12 Python
django rest framework serializer返回时间自动格式化方法
2020/03/31 Python
Python 实现将numpy中的nan和inf,nan替换成对应的均值
2020/06/08 Python
记一次python 爬虫爬取深圳租房信息的过程及遇到的问题
2020/11/24 Python
私人会所最新创业计划书范文
2014/03/24 职场文书
倡议书范文
2014/04/16 职场文书
珍爱生命演讲稿
2014/05/10 职场文书
会计专业自荐书
2014/07/08 职场文书
2014工程部年度工作总结
2014/12/17 职场文书
教师个人自我评价
2015/03/04 职场文书
详解Python牛顿插值法
2021/05/11 Python
VS2019连接MySQL数据库的过程及常见问题总结
2021/11/27 MySQL
Golang 并发下的问题定位及解决方案
2022/03/16 Golang
Go web入门Go pongo2模板引擎
2022/05/20 Golang