zoukankan      html  css  js  c++  java
  • Angular2组件与指令的小实践——实现一个图片轮播组件

    如果说模块系统是Angular2的灵魂,那其组件体系就是其躯体,在模块的支持下渲染出所有用户直接看得见的东西,一个项目最表层的东西就是组件呈现的视图。
    而除了直接看的见的躯体之外,一个完整的“生物”还需要有感觉器官,用来感知外界与其的交互,这就是指令要做的事情。
    本文将使用Angular2提供的强大的组件与指令等功能制作出一个简单的图片轮播控件,继续上文打的比方的话这就像是一个“器官”,功能是呈现图片,并感知用户的点击或手势来切换图片。

    一、创建组件

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

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

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

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

     1 @Component({
     2     selector: 'my-slide-img',
     3     templateUrl: 'slide-img.component.html',
     4     styleUrls: ['slide-img.component.css'],
     5     animations: [
     6         trigger('imgMove', [
     7             /** 不显示 */
     8             state('off', style({'display': 'none', 'z-index': '0', 'transform': 'translateX(0)'})),
     9             /** 上一张图片 */
    10             state('prev', style({'z-index': '1',
    11             'transform': 'translateX(-100%)'})),
    12             /** 下一张图片 */
    13             state('next', style({'z-index': '2', 'transform': 'translateX(100%)'})),
    14             /** 当前图片 */
    15             state('on', style({'z-index': '3', 'transform': 'translateX(0)'})),
    16             transition('prev=>on', [
    17                 animate('0.3s ease-in')
    18             ]),
    19             transition('next=>on', [
    20                 animate('0.3s ease-in')
    21             ]),
    22             transition('on=>prev', [
    23                 animate('0.3s ease-in')
    24             ]),
    25             transition('on=>next', [
    26                 animate('0.3s ease-in')
    27             ])
    28         ])
    29     ]
    30 })
    31 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会直接将其隐藏,实现的逻辑很简单,考虑也不算周全,笔者就不继续解释献丑了。

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

    1 <div class="imgs">
    2     <img src="{{img.Url}}" alt="" class="img"
    3     *ngFor="let img of imgs;let i=index"
    4     (mySwipe)="Change($event)"
    5     [@imgMove]="ImgState(i)">
    6 </div>
    7 
    8 <div class="btn" (click)="Prev()">Prev</div>
    9 <div class="btn" (click)="Next()">Next</div>
    slide-img.component.html
     1 .imgs{
     2     position: relative;width: 100%;height: 15em;
     3     overflow: hidden;
     4 }
     5 .img{
     6     position: absolute;
     7     width: 100%;
     8     height: 100%;
     9     background: pink;
    10     transition: 0.2s;
    11 }
    12 .btn{
    13     display: inline-block;
    14     padding: 1em 2em;font-size: 1em;border-radius: 0.5em;
    15     border: 1px solid #ddd;cursor: pointer;
    16     margin: 1em;background: #eee;box-shadow: 0.1em 0.1em 0.2em #aaa;
    17 }
    18 .btn:active{
    19     background: #eee;
    20     box-shadow: none;
    21 }
    slide-img.component.css
     1 import { Component, OnInit, Input,
     2     animate,
     3     style,
     4     transition,
     5     trigger,
     6     state,
     7     HostListener
     8 } from '@angular/core';
     9 import { SlideImg } from './slide-img.interface';
    10 
    11 @Component({
    12     selector: 'my-slide-img',
    13     templateUrl: 'slide-img.component.html',
    14     styleUrls: ['slide-img.component.css'],
    15     animations: [
    16         trigger('imgMove', [
    17             /** 不显示 */
    18             state('off', style({'display': 'none', 'z-index': '0', 'transform': 'translateX(0)'})),
    19             /** 上一张图片 */
    20             state('prev', style({'z-index': '1',
    21             'transform': 'translateX(-100%)'})),
    22             /** 下一张图片 */
    23             state('next', style({'z-index': '2', 'transform': 'translateX(100%)'})),
    24             /** 当前图片 */
    25             state('on', style({'z-index': '3', 'transform': 'translateX(0)'})),
    26             transition('prev=>on', [
    27                 animate('0.3s ease-in')
    28             ]),
    29             transition('next=>on', [
    30                 animate('0.3s ease-in')
    31             ]),
    32             transition('on=>prev', [
    33                 animate('0.3s ease-in')
    34             ]),
    35             transition('on=>next', [
    36                 animate('0.3s ease-in')
    37             ])
    38         ])
    39     ]
    40 })
    41 export class SlideImgComponent {
    42     @Input() public imgs: SlideImg[];
    43     public current;
    44     constructor() {
    45         this.current = 0;
    46     }
    47     public ImgState(index) {
    48         if (this.imgs && this.imgs.length) {
    49             if (this.current === 0) {
    50                 return index === 0 ? 'on' :
    51                 index === 1 ? 'next' :
    52                 index === this.imgs.length - 1 ? 'prev' :
    53                 'off';
    54             } else if (this.current === this.imgs.length - 1) {
    55                 return index === this.imgs.length - 1 ? 'on' :
    56                 index === this.imgs.length - 2 ? 'prev' :
    57                 index === 0 ? 'next' :
    58                 'off';
    59             }
    60             switch (index - this.current) {
    61                 case 0:
    62                     return 'on';
    63                 case 1:
    64                     return 'next';
    65                 case -1:
    66                     return 'prev';
    67                 default:
    68                     return 'off';
    69             }
    70         } else {
    71             return 'off';
    72         }
    73     }
    74     public Next() {
    75         this.current = (this.current + 1) % this.imgs.length;
    76     }
    77     public Prev() {
    78         this.current = this.current - 1 < 0 ? this.imgs.length - 1 : this.current - 1;
    79     }
    80 
    81     public Change(e) {
    82         if (e === 'left') {
    83             this.Next();
    84         } else if (e === 'right') {
    85             this.Prev();
    86         }
    87     }
    88 }
    slide-img.component.ts

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

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

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

     1 import { Directive, Input, HostListener, Output, EventEmitter } from '@angular/core';
     2 
     3 @Directive({ selector: '[mySwipe]' })
     4 export class SwipeDirective {
     5     @Output() public mySwipe = new EventEmitter<string>();
     6 
     7     private touchStartX;
     8     private touchStartY;
     9     @HostListener('touchstart', ['$event']) public onTouchStart(e) {
    10         this.touchStartX = e.changedTouches[0].clientX;
    11         this.touchStartY = e.changedTouches[0].clientY;
    12     }
    13     @HostListener('touchend', ['$event']) public onTouchEnd(e) {
    14         let moveX = e.changedTouches[0].clientX - this.touchStartX;
    15         let moveY = e.changedTouches[0].clientY - this.touchStartY;
    16         if (Math.abs(moveY) < Math.abs(moveX)) {
    17             /**
    18              * Y轴移动小于X轴 判定为横向滑动
    19              */
    20             if (moveX > 50) {
    21                 this.mySwipe.emit('right');
    22             } else if (moveX < -50) {
    23                 this.mySwipe.emit('left');
    24             }
    25         } else if (Math.abs(moveY) > Math.abs(moveX)) {
    26             /**
    27              * Y轴移动大于X轴 判定为纵向滑动
    28              */
    29             if (moveY > 50) {
    30                 this.mySwipe.emit('down');
    31             } else if (moveY < -50) {
    32                 this.mySwipe.emit('up');
    33             }
    34         }
    35         this.touchStartX = this.touchStartY = -1;
    36     }
    37 }
    swipe.directive.ts

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

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

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

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

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

    总结以及题外话:

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

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

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

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

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

  • 相关阅读:
    frame框架 超链接
    SQL与ASP日期时间
    J2EE开发环境搭建(3)——保存你搭建好的J2EE开发环境
    td.moveRow方法
    SQL Management Studio Express 安装缺少MSXML6解决
    js tr onmouseover时改变该行背景色
    doc 解决windows系统中名字为eula的文件无法被删除
    js select 隐藏option
    LLBLGen的数据库相对应SQL语句实现方法收藏
    使用 DataAdapter 更新数据源
  • 原文地址:https://www.cnblogs.com/yitim/p/angular2-component-slide-img.html
Copyright © 2011-2022 走看看