第一節(jié):初識Angular-CLI
第二節(jié):登錄組件的構(gòu)建
第三節(jié):建立一個待辦事項應(yīng)用
第四節(jié):進化恐疲!模塊化你的應(yīng)用
第五節(jié):多用戶版本的待辦事項應(yīng)用
第六節(jié):使用第三方樣式庫及模塊優(yōu)化用
第七節(jié):給組件帶來活力
Rx--隱藏在Angular 2.x中利劍
Redux你的Angular 2應(yīng)用
第八節(jié):查缺補漏大合集(上)
第九節(jié):查缺補漏大合集(下)
第九章:查缺補漏大合集(下)
Angular2 動畫再體驗
State和Transition
我寫文章的習(xí)慣是先試驗再理論,所以我們接下來梳理下Angular2提供的動畫技能套么。還是從最簡單的例子開始培己,一個非常簡單的模版:
<div class="traffic-light"></div>
同樣非常簡單的樣式(其實就是畫一個小黑塊):
.traffic-light{
width: 100px;
height: 100px;
background-color: black;
}
現(xiàn)在的效果就是這個樣子,如圖所示胚泌,一點都不酷啊漱凝,沒關(guān)系,我們一點點來诸迟,越簡單的越容易弄懂概念。
下面我們?yōu)榻M件添加一個animations的元數(shù)據(jù)描述:
import {
Component,
trigger,
state,
style
} from '@angular/core';
@Component({
selector: 'app-playground',
templateUrl: './playground.component.html',
styleUrls: ['./playground.component.css'],
animations: [
trigger('signal', [
state('go', style({
'background-color': 'green'
}))
])
]
})
export class PlaygroundComponent {
constructor() { }
}
我們注意到animations中接受的是一個數(shù)組愕乎,這個數(shù)組里面我們使用了一個叫trigger的函數(shù)阵苇,trigger接受的第一個參數(shù)是觸發(fā)器的名字,第二個參數(shù)是一個數(shù)組感论。這個數(shù)組是由一種叫state的函數(shù)和叫transition的函數(shù)組成的绅项。
那么什么是state?state表示一種狀態(tài)比肄,當(dāng)這種狀態(tài)激活時快耿,state所附帶的樣式就會附著在應(yīng)用trigger的那個控件上囊陡。transition又是什么呢?tranistion描述了一系列動畫的步驟掀亥,在狀態(tài)遷移時這些動畫步驟就會執(zhí)行撞反。
我們現(xiàn)在的這個版本中暫時只有state而沒有transition,讓我們先來看看效果搪花,當(dāng)然在可以看到效果前我們先要把這個trigger應(yīng)用到某個控件上遏片。那在我們的例子里就是模版中的那個div了。
<div
[@signal]="'go'"
class="traffic-light">
</div>
返回瀏覽器撮竿,你會發(fā)現(xiàn)那個小黑塊變成小綠塊了吮便,如圖所示
這說明什么?我們的state的樣式附著在div上了幢踏。為什么呢髓需?因為 [@signal]="'go'" 定義了trigger的狀態(tài)是go。但這一點也不酷是嗎房蝉?是的僚匆,暫時是這樣,還是那句話惨驶,不要急白热。
接下來,我們再加一個狀態(tài) stop粗卜,在stop激活時我們要把小方塊的背景色設(shè)為紅色屋确,那么我們需要把animations改成下面的樣子:
animations: [
trigger('signal', [
state('go', style({
'background-color': 'green'
})),
state('stop', style({
'background-color':'red'
}))
])
]
同時我們需要給模板加兩個按鈕Go和Stop。現(xiàn)在的模版看起來是下面的樣子
<div
[@signal]="signal"
class="traffic-light">
</div>
<button (click)="onGo()">Go</button>
<button (click)="onStop()">Stop</button>
當(dāng)然你看得到续扔,我們點擊按鈕時需要處理對應(yīng)的點擊事件攻臀。在這里我們希望點擊Go時,方塊變綠纱昧,點擊Stop時方塊變紅刨啸。如果要達成這個目的,我們需要一個叫signal的成員變量识脆,在點擊的處理函數(shù)中更改相應(yīng)的狀態(tài)设联。
export class PlaygroundComponent {
signal: string;
constructor() { }
onGo(){
this.signal = 'go';
}
onStop(){
this.signal = 'stop';
}
}
現(xiàn)在打開瀏覽器,試驗一下灼捂,我們會發(fā)現(xiàn)點擊Go變綠离例,而點擊Stop變紅。但是還是沒動起來啊悉稠,是的宫蛆,這是因為我們還沒加transition呢,我們只需把animations改寫一下的猛,你分別點Go和Stop就能看到動畫效果了耀盗。為了讓效果更明顯一些想虎,我們?yōu)閮煞N狀態(tài)指定一下高度。
import {
Component,
OnDestroy,
trigger,
state,
style,
transition,
animate
} from '@angular/core';
@Component({
selector: 'app-playground',
templateUrl: './playground.component.html',
styleUrls: ['./playground.component.css'],
animations: [
trigger('signal', [
state('void', style({
'transform':'translateY(-100%)'
})),
state('go', style({
'background-color': 'green',
'height':'100px'
})),
state('stop', style({
'background-color':'red',
'height':'50px'
})),
transition('void => *', animate(5000))
])
]
})
export class PlaygroundComponent {
signal: string;
constructor() { }
onGo(){
this.signal = 'go';
}
onStop(){
this.signal = 'stop';
}
}
那么 transition('* => *', animate(500))
這句什么意思呢叛拷?前面那個 '* => *'
是一個狀態(tài)遷移表達式舌厨,*
表示任意狀態(tài),所以這個表達式告訴我們胡诗,只要有狀態(tài)的變化就會激發(fā)后面的動畫效果邓线。后面的就是告訴Angular做500毫秒的動畫,這個動畫默認是從一個狀態(tài)過渡到另一個狀態(tài)』突郑現(xiàn)在大家打開瀏覽器體驗一下骇陈,分別點擊Go和Stop,會發(fā)現(xiàn)我們的小方塊從一個正方形變成一個長方形瑰抵,紅色變成綠色的過程你雌。體驗完之后再來看這句話:動畫其實就是由若干個狀態(tài)組成,由transition定義狀態(tài)過渡的步驟二汛。
那么下面我們介紹一個void 狀態(tài)(空狀態(tài))婿崭,為什么會有void狀態(tài)呢?其實剛剛我們也體驗了肴颊,只不過沒有定義這個void 狀態(tài)而已氓栈。我們在組件中并沒有給signal賦初始值,這就意味著一開始trigger的狀態(tài)就是void婿着。我們往往在實現(xiàn)進場或離場動畫時需要這個void狀態(tài)授瘦。void狀態(tài)就是描述沒有狀態(tài)值時的狀態(tài)。
animations: [
trigger('signal', [
state('void', style({
'transform':'translateY(-100%)'
})),
state('go', style({
'background-color': 'green',
'height':'100px'
})),
state('stop', style({
'background-color':'red',
'height':'50px'
})),
transition('* => *', animate(500))
])
]
上面代碼定義了一個void狀態(tài)竟宋,而且樣式上有一個按Y軸做的-100%的位移提完,其實這就是一開始讓小方塊從場景外進入場景內(nèi),這樣就是實現(xiàn)了一種進場動畫丘侠,大家可以在瀏覽器中試驗一下徒欣。
奇妙的animate函數(shù)
上面的我們的實驗中,你會發(fā)現(xiàn)transition中有個animate函數(shù)蜗字,可能你認為它就是指定一個動畫的時間的函數(shù)打肝。它的身手可不止那么簡單呢,我們來仔細挖掘一下挪捕。
首先呢闯睹,我們來對上面的代碼做一個小改造,把animations數(shù)組改成下面的樣子:
animations: [
trigger('signal', [
state('void', style({
'transform':'translateY(-100%)'
})),
state('go', style({
'background-color': 'green',
'height':'100px'
})),
state('stop', style({
'background-color':'red',
'height':'50px'
})),
transition('* => *', animate('.5s 1s'))
])
]
我們其實只對animate中的參數(shù)做了一點小改動担神,就是把animate(500) 改成animate('.5s 1s')。那么.5s表示動畫過渡時間為0.5秒(其實和上面設(shè)置的500毫秒是一樣的)始花,1s表示動畫延遲1秒后播放⊥叮現(xiàn)在我們打開瀏覽器孩锡,看看效果如何吧。
當(dāng)然還有更狠的大招亥贸,這個字符串表達式還可以變成 '.5s 1s ease-out'躬窜,后面的這個ease-out是一種緩動函數(shù),它是可以讓動畫效果更真實的一種方式炕置。
現(xiàn)實世界中物體照著一定節(jié)奏移動荣挨,并不是一開始就移動很快的,也不可能是一直勻速運動的朴摊。怎么理解呢默垄?當(dāng)皮球往下掉時,首先是越掉越快甚纲,撞到地上后回彈口锭,最終才又碰觸地板。而緩動函數(shù)可以使動畫的過渡效果按照這樣的真實場景抽象出的對應(yīng)函數(shù)來進行繪制介杆。ease-out只是眾多的緩動函數(shù)的其中一種鹃操,我們當(dāng)然可以指定其他函數(shù)。
另外需要說明的一點是諸如ease-out只是真實函數(shù)的一個友好名稱春哨,我們當(dāng)然可以直接指定背后的函數(shù):cubic-bezier(0, 0, 0.58, 1) 荆隘。我們下個小例子不用這個ease-out,因為效果可能不是特別明顯赴背,我們找一個明顯的椰拒,使用 cubic-bezier(0.175, 0.885, 0.32, 1.275) 。現(xiàn)在我們打開瀏覽器癞尚,你仔細觀察一下是否看到了小方塊回彈的效果
animations: [
trigger('signal', [
state('void', style({
'transform':'translateY(-100%)'
})),
state('go', style({
'background-color': 'green',
'height':'100px'
})),
state('stop', style({
'background-color':'red',
'height':'50px'
})),
transition('* => *', animate('.5s 1s cubic-bezier(0.175, 0.885, 0.32, 1.275)'))
])
]
關(guān)于緩動函數(shù)的更多資料可以訪問 http://easings.net/zh-cn 在這里可以看到各種函數(shù)的曲線和效果耸三,以及cubic-bezier函數(shù)的各種參數(shù)
需要注意的一點是Angular2實現(xiàn)動畫的機制其實是基于W3C的Web Animation標(biāo)準(zhǔn),這個標(biāo)準(zhǔn)暫時無法支持所有的cubic-bezier函數(shù)浇揩,只有部分函數(shù)被支持仪壮。這樣的話我們?nèi)绻獙崿F(xiàn)某些不被支持的函數(shù)怎么辦呢?那就得有請我們的關(guān)鍵幀出場了胳徽。
關(guān)鍵幀
何謂關(guān)鍵幀积锅?首先需要知道什么是幀?百度百科給了定義:
幀——就是動畫中最小單位的單幅影像畫面养盗,相當(dāng)于電影膠片上的每一格鏡頭缚陷。在動畫軟件的時間軸上幀表現(xiàn)為一格或一個標(biāo)記。
關(guān)鍵幀——相當(dāng)于二維動畫中的原畫往核。指角色或者物體運動或變化中的關(guān)鍵動作所處的那一幀箫爷。關(guān)鍵幀與關(guān)鍵幀之間的動畫可以由軟件來創(chuàng)建,叫做過渡幀或者中間幀。
先來做一個小實驗虎锚,我們把入場動畫改造成關(guān)鍵幀形式野揪。
import {
Component,
OnDestroy,
trigger,
state,
style,
transition,
animate,
keyframes
} from '@angular/core';
@Component({
selector: 'app-playground',
templateUrl: './playground.component.html',
styleUrls: ['./playground.component.css'],
animations: [
trigger('signal', [
state('void', style({
'transform':'translateY(-100%)'
})),
state('go', style({
'background-color': 'green',
'height':'100px'
})),
state('stop', style({
'background-color':'red',
'height':'50px'
})),
transition('void => *', animate(5000, keyframes([
style({'transform': 'scale(0)'}),
style({'transform': 'scale(0.1)'}),
style({'transform': 'scale(0.5)'}),
style({'transform': 'scale(0.9)'}),
style({'transform': 'scale(0.95)'}),
style({'transform': 'scale(1)'})
]))),
transition('* => *', animate('.5s 1s cubic-bezier(0.175, 0.885, 0.32, 1.275)'))
])
]
})
export class PlaygroundComponent {
// clock = Observable.interval(1000).do(_=>console.log('observable created'));
signal: string;
constructor() { }
onGo(){
this.signal = 'go';
}
onStop(){
this.signal = 'stop';
}
}
保存后返回瀏覽器吮成,你應(yīng)該可以看到一個正方形由小變大的進場動畫。
現(xiàn)在我們來分析一下代碼,這個入場動畫是5秒的時間珍语,我們給出6個關(guān)鍵幀御吞,也就是0s席怪,1s畜普,2s,3s护侮,4s和5s這幾個敌完。對于每個關(guān)鍵幀,我們給出的樣式都是放縮概行,而放縮的比例逐漸加大蠢挡,而且是先快后慢,也就是說我們可以模擬出緩動函數(shù)的效果凳忙。
如果我們不光做放縮业踏,而且在style中還指定位置的話,這個動畫就會出現(xiàn)邊移動邊變大的效果了涧卵。把入場動畫改成下面的樣子試試看吧勤家。
transition('void => *', animate(5000, keyframes([
style({'transform': 'scale(0)', 'padding': '0px'}),
style({'transform': 'scale(0.1)', 'padding': '50px'}),
style({'transform': 'scale(0.5)', 'padding': '100px'}),
style({'transform': 'scale(0.9)', 'padding': '120px'}),
style({'transform': 'scale(0.95)', 'padding': '135px'}),
style({'transform': 'scale(1)', 'padding': '140px'})
]))),
最后的結(jié)果可能還是不酷,但是這樣的話利用關(guān)鍵幀我們?nèi)绻Y(jié)合好CSS樣式柳恐,就會做出比較復(fù)雜的動畫了伐脖。
方便的管道--PIPE
我們一直沒有提到的一點就是管道,雖然我們的例子中沒有用到乐设,但其實這是Angular 2中提供非常方便的一個特性讼庇。這個特性可以讓我們很快的將數(shù)據(jù)在界面上以我們想要的格式輸出出來。還是拿例子說話近尚,比如我們在頁面上顯示一個日期蠕啄,先建立一個簡單的模版:
<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
再來建立對應(yīng)的組件文件:
import { Component, OnDestroy } from '@angular/core';
@Component({
selector: 'app-playground',
templateUrl: './playground.component.html',
styleUrls: ['./playground.component.css']
})
export class PlaygroundComponent {
birthday = new Date();
constructor() { }
}
上面的例子可能還沒太明顯,我們 進一步改造一下模板:
<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
<p>The time is {{ birthday | date:'shortTime' }}</p>
<p>The time is {{ birthday | date:'medium' }}</p>
而且更牛的是多個Pipes可以串起來使用戈锻,比如說上圖中最下面那個日期我們希望把Dec大寫歼跟,就可以這樣使用:
<p>The time is {{ birthday | date:'medium' | uppercase }}</p>
自定義一個Pipe
那么自己寫一個Pipe是怎樣的體驗?zāi)兀縿?chuàng)建一個Pipe非常簡單格遭,我們來體會一下哈街。首先創(chuàng)建一個 src/app/playground/trim-space.pipe.ts
的文件:
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'trimSpace'
})
export class TrimSpacePipe implements PipeTransform {
transform(value: any, args: any[]): any {
return value.replace(/ /g, '');
}
}
在Module文件中聲明這個Pipe:declarations: [PlaygroundComponent, TrimSpacePipe]
以便于其他控件可以使用這個Pipe:
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { PlaygroundRoutingModule } from './playground-routing.module';
import { PlaygroundComponent } from './playground.component';
import { PlaygroundService } from './playground.service';
import { TrimSpacePipe } from './trim-space.pipe';
@NgModule({
imports: [
SharedModule,
PlaygroundRoutingModule
],
providers:[
PlaygroundService
],
declarations: [PlaygroundComponent, TrimSpacePipe]
})
export class PlaygroundModule { }
然后在組件的模板文件中使用即可 {{ birthday | date:'medium' | trimSpace}}
:
<p> Without Pipe: Today is {{ birthday }} </p>
<p> With Pipe: Today is {{ birthday | date:"MM/dd/yy" }} </p>
<p>The time is {{ birthday | date:'shortTime' }}</p>
<p>The time is {{ birthday | date:'medium' | trimSpace}} with trim space pipe applied</p>
<p>The time is {{ birthday | date:'medium' | uppercase }}</p>
打開瀏覽器看一下效果,我們看到應(yīng)用了trimSpace管道的日期的空格被移除了拒迅,如圖所示:
內(nèi)建的Pipe
Decimal Pipe
DatePipe和UpperCase Pipe我們剛剛已經(jīng)見識過了骚秦,現(xiàn)在我們看一看內(nèi)建的其他Pipe她倘。首先是用于數(shù)字格式化的DecimalPipe。DecimalPipe的參數(shù)是以 {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
的表達式形式體現(xiàn)的作箍。其中:
- minIntegerDigits 是最小的整數(shù)位數(shù)帝牡,默認是1。
- minFractionDigits 表示最小的小數(shù)位數(shù)蒙揣,默認是0。
- maxFractionDigits 表示最大的小數(shù)位數(shù)开瞭,默認是3懒震。
<p>pi (no formatting): {{pi}}</p>
<p>pi (.5-5): {{pi | number:'.5-5'}}</p>
<p>pi (2.10-10): {{pi | number:'2.10-10'}}</p>
<p>pi (.3-3): {{pi | number:'.3-3'}}</p>
如果我們在組件中定義 pi: number = 3.1415927;
的話,上面的數(shù)字會被格式化成下圖的樣子
Currency Pipe
顧名思義嗤详,這個Pipe是格式化貨幣的个扰,這個Pipe的表達式形式是這樣的: currency[:currencyCode[:symbolDisplay[:digitInfo]]]
,也就是說在currency管道后用分號分隔不同的屬性設(shè)置:
<p>A in USD: {{a | currency:'USD':true}}</p>
<p>B in CNY: {{b | currency:'CNY':false:'4.2-2'}}</p>
上面的代碼中 USD
或 CNY
表面貨幣代碼葱色,true
或 false
表明是否使用該貨幣的默認符號递宅,后面如果再有一個表達式就是規(guī)定貨幣的位數(shù)限制。這個限制的具體規(guī)則和上面Decimal Pipe的類似苍狰,如下圖所示办龄。
Percent Pipe
這個管道當(dāng)然就是用來格式化百分?jǐn)?shù)的,百分?jǐn)?shù)的整數(shù)位和小數(shù)位的規(guī)則也和上面提到的Decimal Pipe和Currency Pipe一致淋昭。如果在組件中定義 myNum: number = 0.1415927
; 下面的代碼會輸出成下圖的樣子:
<p>myNum : {{myNum | percent}}</p>
<p>myNum (3.2-2) : {{myNum | percent:'3.2-2'}}</p>
Json Pipe
這個管道個人感覺更適合在調(diào)試中使用俐填,它可以把任何對象格式化成JSON格式輸出。如果我們在組件中定義了一個對象:
object: Object = {
foo: 'bar',
baz: 'qux',
nested: {
xyz: 3,
numbers: [1, 2, 3, 4, 5]
}
};
那么下面的模板會輸出下圖的樣子翔忽,在調(diào)試階段英融,這個特性很好幫助你輸出可讀性很強的對象格式。當(dāng)然如果你使用了現(xiàn)代化的IDE歇式,這么使用的意義就不是很大了:
<div>
<p>Without JSON pipe:</p>
<pre>{{object}}</pre>
<p>With JSON pipe:</p>
<pre>{{object | json}}</pre>
</div>
指令——Directive
另一個我們一直沒有提到的重要概念就是指令了驶悟,但這個雖然我們沒提到,卻已經(jīng)用過了材失。比如 *ngFor
痕鳍,*ngIf
等,這些都叫做結(jié)構(gòu)性指令豺憔,而像 *ngModel
等屬于屬性型指令额获。
Angular 2中的指令分成三種:結(jié)構(gòu)型(Structural)指令和屬性型(Attribute)指令,還有一種是什么呢恭应?就是Component抄邀,組件本身就是一個帶模板的指令。
結(jié)構(gòu)型指令可以通過添加昼榛、刪除DOM元素來更改DOM樹的布局境肾,比如我們前面使用 *ngFor
在todo-list的模板中添加了多個todo-item剔难。而屬性型指令可以改變一個DOM元素的外觀或行為,比如我們利用 *ngModel
進行雙向綁定奥喻,改變了該組件的默認行為(我們在組件中改變某個變量值偶宫,這種改變會直接反應(yīng)到組件上,這并不是組件自身定義的行為环鲤,而是我們通過 *ngModel
來改變的)纯趋。
Angular 2中給出的內(nèi)建結(jié)構(gòu)型指令如下表所示:
名稱 | 用法 | 說明 |
---|---|---|
ngIf | <div*ngIf="canShow"> |
基于canShow表達式的值移除或重新創(chuàng)建部分DOM樹。 |
ngFor | <li *ngFor="let todo of todos"> |
把li元素及其內(nèi)容轉(zhuǎn)化成一個模板冷离,并用它來為列表中的每個條目初始化視圖吵冒。 |
ngSwitch, ngSwitchCase, ngSwitchDefault | <div [ngSwitch]="someCondition"></div> |
基于someCondition的當(dāng)前值,從內(nèi)嵌模板中選取一個西剥,有條件的切換div的內(nèi)容痹栖。 |
自定義一個指令也很簡單,我們動手做一個瞭空。這個指令非常簡單就是使任何控件加上這個指令后揪阿,其點擊動作都會在console中輸出 “I am clicked”。由于我們要監(jiān)視其宿主的click事件咆畏,所以我們引入了 HostListener南捂,在onClick方法上用 @HostListen(‘click’)
,表明在檢測到宿主發(fā)生click事件時調(diào)用這個方法鳖眼。
import {
Directive,
HostListener
} from '@angular/core';
@Directive({
selector: "[log-on-click]",
})
export class LogOnClickDirective {
constructor() {}
@HostListener('click')
onClick() { console.log('I am clicked!'); }
}
在模板中簡單寫一句就可以看效果了
<button log-on-click>Click Me</button>
代碼: https://github.com/wpcfan/awesome-tutorials/tree/master/angular2/ng2-tut
慕課網(wǎng) Angular 視頻課上線: http://coding.imooc.com/class/123.html?mc_marking=1fdb7649e8a8143e8b81e221f9621c4a&mc_channel=banner
紙書出版了黑毅,比網(wǎng)上內(nèi)容豐富充實了,歡迎大家訂購钦讳!
京東鏈接:https://item.m.jd.com/product/12059091.html?from=singlemessage&isappinstalled=0
第一節(jié):初識Angular-CLI
第二節(jié):登錄組件的構(gòu)建
第三節(jié):建立一個待辦事項應(yīng)用
第四節(jié):進化矿瘦!模塊化你的應(yīng)用
第五節(jié):多用戶版本的待辦事項應(yīng)用
第六節(jié):使用第三方樣式庫及模塊優(yōu)化用
第七節(jié):給組件帶來活力
Rx--隱藏在Angular 2.x中利劍
Redux你的Angular 2應(yīng)用
第八節(jié):查缺補漏大合集(上)
第九節(jié):查缺補漏大合集(下)