Angular 2 動畫 (ANIMATIONS)
- 2016-12-25
- Liu, An-Chi 劉安齊
前言
動畫在現代網頁中無所不在。好的使用者介面,裡面的動畫應該要看起來很平順自然,必要而不花俏,這樣才能帶給使用者最佳的體驗,並且在某些重要的地方讓使用者注意到。所以一個有好的設計的動畫頁面,可以讓 UI 更有趣且更容易使用。
Angular 可以製造出跟純 CSS 動畫一樣的效果. 我們可以輕鬆的用程式碼來控制這些動畫效果,搭配一些邏輯放入我們的網頁應用程式。
Angular 動畫使用的模組已經內建在大多數瀏覽器,若是不支援要加入 web-animations.min.js
接下來我們竟來建立 component,來做到用淡入的效果顯示和隱藏內容,並讓其他外部的 component 也可以輕易的觸發他們的淡入效果。
準備工具
先來看看做動畫需要甚麼吧! 畢竟巧婦難為無米之炊。
首先要引入這些東西
import {
Component,
Input,
trigger,
state,
style,
transition,
animate
} from '@angular/core';
場景
先來看看簡單的隱藏顯示,並未加入動畫。
@Component({
selector: 'my-fader',
template: `
<div *ngIf="visibility == 'shown'" >
<ng-content></ng-content>
Can you see me?
</div>
`
})
export class MyComponent implements OnChanges {
visibility = 'shown';
@Input() isVisible : boolean = true;
ngOnChanges() {
this.visibility = this.isVisible ? 'shown' : 'hidden';
}
}
這邊用 @Input() isVisible
屬性來達大顯示和隱藏的效果。(Plurk)
加料
只是單純的顯示和隱藏多麼地無趣呀!我們想要有淡入或淡出的效果。
來幫 Component 加點料吧!
@Component({
...,
template : ``,
animations: [
...
]
)]
class MyComponent() { ... }
animations
用來宣告動畫,和 template
一樣屬於 metadata。
因為我們的動畫是 visibility
的屬性變化,所以我們的動畫設定由其值變化而觸發。
animations: [
trigger('visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 }))
])
]
精神一切盡在程式碼中。
但是剛剛的設定,是瞬間變化,而我們希望的是慢一點的樣子
animations: [
trigger(’visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('* => *', animate('.5s'))
])
]
這樣不管淡入 (opacity 1 到 0 ) 或是淡出 (opacity 0 到 1 ) 都會經過 500ms,而 animate
也可以這樣表示 animate('500ms')
。
transition('* => *', ...)
代表甚麼意思呢?*
代表任何狀態,意思就是可以是 shown
或 hidden
。當然也可以直接指定。
animations: [
trigger(’visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('shown => hidden', animate('600ms')),
transition('hidden => shown', animate('300ms')),
])
]
這樣我們就把淡入和淡出做區隔,兩者時間花的不一樣長。
完工
接下來要把動畫和 Compoent 連起來。 visibilityChanged
是如何和 component 連結?動畫又是如何和和 component 的屬性作綁定?
我們可以在模板裡這樣做,就像許多的數據綁定模式雷同。
<div [@visibilityChanged]="visibility">
Can you see me? I should fade in or out...
</div>
完整版就會長得像這樣子
import {
Component, OnChanges, Input,
trigger, state, animate, transition, style
} from '@angular/core';
@Component({
selector : 'my-fader',
animations: [
trigger('visibilityChanged', [
state('shown' , style({ opacity: 1 })),
state('hidden', style({ opacity: 0 })),
transition('* => *', animate('.5s'))
])
],
template: <div [@visibilityChanged]="visibility" > <ng-content></ng-content> <p>Can you see me? I should fade in or out...</p> </div>
})
export class FaderComponent implements OnChanges {
@Input() isVisible : boolean = true;
visibility = 'shown';
ngOnChanges() {
this.visibility = this.isVisible ? 'shown' : 'hidden';
}
}
簡化
用 [@visibilityChanged]="isVisible"
而不是 [@visibilityChanged]="visibility"
的話,可以不用到 ngOnChanges
,意思就是程式碼可以更精簡。但要注意的是 isVisible 的狀態是 true
和 false
import {
Component, OnChanges, Input,
trigger, state, animate, transition, style
} from '@angular/core';
@Component({
selector : 'my-fader',
animations: [
trigger('visibilityChanged', [
state('true' , style({ opacity: 1, transform: 'scale(1.0)' })),
state('false', style({ opacity: 0, transform: 'scale(0.0)' })),
transition('1 => 0', animate('300ms')),
transition('0 => 1', animate('900ms'))
])
],
template: <div [@visibilityChanged]="isVisible" > <ng-content></ng-content> <p>Can you see me? I should fade in or out...</p> </div>
})
export class FaderComponent implements OnChanges {
@Input() isVisible : boolean = true;
}
(Plurk)
層次
parent component 可以直接控制 child component,這邊的例子就是 <my-fader>
的 isVisible
變化,會直接影響到 child component my-fader
,my-fader
的模板也會跟著一起動,達到我們要的淡入淡出效果。
@Component({
selector : 'my-app',
template: `
<my-fader [isVisible]="showFader">
Am I visible ?
</my-fader>
<button (click)="showFader = !showFader"> Toggle </button>
`
})
export class MyAppComponent {
showFader : boolean = true;
}
(Plurk)
細說 State
wildcard state
還記得剛剛 transition('* => *',...)
嗎? *
就是 wildcard state(通用符號),也就是表示所有狀況,在程式語言中應該蠻常見的。
void state
void
代表沒有東西,可能是被移除或是還沒被產生,void
用在進入和出場時非常好用。舉例來說 * => void
就代表離開畫面的動畫。
- 飛入:
void => *
- 飛出:
* => void
animations: [
trigger('flyInOut', [
state('in', style({transform: 'translateX(0)'})),
transition('void => *', [
style({transform: 'translateX(-100%)'}),
animate(100)
]),
transition('* => void', [
animate(100, style({transform: 'translateX(100%)'}))
])
])
]
上面的程式碼,會先有飛入效果,緊接者飛出。
看起來就會長這樣:
動畫時間控制
Angular 提供三種控制時間效果的屬性,分別是 duration
、 delay
和 easing
。
Duration
- 直接用數字,預設單位為 ms: 100
- 用字串加上 ms: '100ms'
- 用字串加上 s: '0.1s'
Delay
- 等待 100ms 然後動畫運作 200ms: '0.2s 100ms'
Easing
- 等待 100ms 然後運行 200ms 搭配漸慢效果: '0.2s 100ms ease-out'
- 執行 200ms 搭配漸快效果: '0.2s ease-in'
animations: [
trigger('flyInOut', [
state('in', style({opacity: 1, transform: 'translateX(0)'})),
transition('void => *', [
style({
opacity: 0,
transform: 'translateX(-100%)'
}),
animate('0.2s ease-in')
]),
transition('* => void', [
animate('0.2s 10 ease-out', style({
opacity: 0,
transform: 'translateX(100%)'
}))
])
])
]
看起來就會長這樣
大多數的動畫都可以用 Angular Component 的方式做到,而不需要動到 TypeScript 來控制動畫效果,Angular 動畫設計的目的就是讓我們用更直觀,不直接動 DOM 的方式,用 component->template->animation 的方式,讓設計師更好建構和實現動畫。
※更詳盡內容可以參考官方文件