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動畫來實現這種實時滑動的完美解決辦法,實在是尷尬。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM