iT邦幫忙

2017 iT 邦幫忙鐵人賽
DAY 10
1
Modern Web

Angular 2 之 30 天邁向神乎其技之路系列 第 10

[Day 10] Angular 2 動畫 (ANIMATIONS)

前言

動畫在現代網頁中無所不在。好的使用者介面,裡面的動畫應該要看起來很平順自然,必要而不花俏,這樣才能帶給使用者最佳的體驗,並且在某些重要的地方讓使用者注意到。所以一個有好的設計的動畫頁面,可以讓 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('* => *', ...) 代表甚麼意思呢?* 代表任何狀態,意思就是可以是 shownhidden。當然也可以直接指定。

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 的狀態是 truefalse

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-fadermy-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 提供三種控制時間效果的屬性,分別是 durationdelayeasing

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 的方式,讓設計師更好建構和實現動畫。

※更詳盡內容可以參考官方文件


上一篇
[Day 09] Angular 2 路由
下一篇
[Day 11] Angular 2 HTTP 方法--讓我 Call API
系列文
Angular 2 之 30 天邁向神乎其技之路31

尚未有邦友留言

立即登入留言