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实现的鼠标链接提示效果生成器代码
Jun 28 Javascript
JS解密入门 最终变量劫持
Jun 25 Javascript
jQuery插件之Tocify动态节点目录菜单生成器附源码下载
Jan 08 Javascript
深入解析Javascript闭包的功能及实现方法
Jul 10 Javascript
关于动态执行代码(js的Eval)实例详解
Aug 15 Javascript
Vue 单文件中的数据传递示例
Mar 21 Javascript
12个非常有用的JavaScript技巧
May 17 Javascript
JavaScript插件Tab选项卡效果
Nov 14 Javascript
解决vue-cli3 使用子目录部署问题
Jul 19 Javascript
vue实现密码显示与隐藏按钮的自定义组件功能
Apr 23 Javascript
详解vue-cli3多页应用改造
Jun 04 Javascript
vue中解决微信html5原生ios虚拟键返回不刷新问题
Oct 20 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
php5 non-thread-safe和thread-safe这两个版本的区别分析
2010/03/13 PHP
php存储过程调用实例代码
2013/02/03 PHP
自制PHP框架之模型与数据库
2017/05/07 PHP
tp5框架内使用tp3.2分页的方法分析
2019/05/05 PHP
Javascript 自适应高度的Tab选项卡
2011/04/05 Javascript
jQuery点缩略图弹出层显示大图片
2015/02/13 Javascript
JavaScript判断按钮被点击的方法
2015/12/13 Javascript
jQuery EasyUI提交表单验证
2016/07/19 Javascript
微信小程序 UI布局常用技巧整理总结
2016/12/05 Javascript
详解Web使用webpack构建前端项目
2017/09/23 Javascript
React学习之事件绑定的几种方法对比
2017/09/24 Javascript
Angular简单验证功能示例
2017/12/22 Javascript
React Native 真机断点调试+跨域资源加载出错问题的解决方法
2018/01/18 Javascript
vue实现未登录跳转到登录页面的方法
2018/07/17 Javascript
原生JS forEach()和map()遍历的区别、兼容写法及jQuery $.each、$.map遍历操作
2019/02/27 jQuery
vue点击自增和求和的实例代码
2019/11/06 Javascript
CKEditor扩展插件:自动排版功能autoformat插件实现方法详解
2020/02/06 Javascript
vue如何在项目中调用腾讯云的滑动验证码
2020/07/15 Javascript
electron踩坑之remote of undefined的解决
2020/10/06 Javascript
matplotlib绘图实例演示标记路径
2018/01/23 Python
Python编程求质数实例代码
2018/01/31 Python
Python切片索引用法示例
2018/05/15 Python
Python  unittest单元测试框架的使用
2018/09/08 Python
python 五子棋如何获得鼠标点击坐标
2019/11/04 Python
使用python从三个角度解决josephus问题的方法
2020/03/27 Python
tensorflow下的图片标准化函数per_image_standardization用法
2020/06/30 Python
解决python便携版无法直接运行py文件的问题
2020/09/01 Python
通过Python pyecharts输出保存图片代码实例
2020/11/25 Python
HTML table 表格边框的实现思路
2019/10/12 HTML / CSS
KIEHL’S科颜氏官方旗舰店:源自美国的顶级护肤品牌
2018/06/07 全球购物
岗位明星事迹材料
2014/05/18 职场文书
自主招生英文自荐信
2015/03/25 职场文书
践行三严三实心得体会(2016推荐篇)
2016/01/06 职场文书
课文《燕子》教学反思
2016/02/17 职场文书
开机音效回归! Windows 11重新引入开机铃声
2021/11/21 数码科技
Pycharm远程调试和MySQL数据库授权问题
2022/03/18 MySQL