Angular(2+)動畫API用法詳解

莫莫莫發表於2019-02-28

動畫相關的 API 都是在 @angular/animations裡面引入的。

import { 你想要的API } from `@angular/animations`;
複製程式碼

trigger : 由它觸發動畫效果,稱動畫觸發器。

function trigger(
            name: string, 
            definitions: AnimationMetadata[]  
         ):AnimationTriggerMetadata;
複製程式碼
  • name

    動畫觸發器名字, 如 growWidth

  • definitions

    要執行的動畫, 如

    [ transition("void => *", [style({opacity: 0}), animate(600, style({ opacity: 1}))] )],

state :定義目標處於某一狀態時的樣式

function state(
            name: string, 
            styles: AnimationStyleMetadata,
            options?: {params: {[name: string]: any}}
         ):AnimationStateMetadata;
複製程式碼
  • name

    當前狀態的名字,可以有多個名字,用逗號隔開,如:`active,clicked`
    這個name還可以用 void*表示: void代表動畫執行前元件的狀態; *代表動畫執行後元件的狀態,即元件的預設狀態。

  • styles

    處於這個狀態時的樣式,如style({width: 0})

  • options : 可選項。

transition:狀態之間轉換處理函式

function transition(
            stateChangeExpr: string, 
            steps: AnimationMetadata | AnimationMetadata[], 
            options: AnimationOptions | null = null
         ):AnimationTransitionMetadata;
複製程式碼
  • stateChangeExpr

    A=>B,狀態轉換表示式,即從哪個狀態切換到哪個狀態。支援以下寫法:

    1. 狀態改變時啟動動畫
     transition("void => *", animate(500))
    複製程式碼
    1. 可以在兩個狀態變化上執行相同動畫
    transition("void <=> *", animate(500)),
    複製程式碼
    1. 也可以定義幾對狀態切換執行同一動畫
     transition("on => off, off => void", animate(500)),
    複製程式碼
  • steps

    animate(),動畫執行步驟,即幾秒執行完,執行曲線是怎樣的。
    animate(`100ms ease-in`) 或是一個陣列 [animate(`100ms ease-in`),animate(600)]

  • options

    可以傳入動畫的延遲值和動畫輸入引數,以更改計時資料和樣式。詳見 AnimationOptions函式。

用法

其實狀態 transition("void => *", animate(500))表示的是進入,在這裡可以用 :enter 表示:

transition(":enter", animate(500))
複製程式碼

同理 transition(" *=> void", animate(500))離開可以這樣寫:

transition(":leave", animate(500)) 
複製程式碼

animate : 動畫

function animate(
            timings: string | number,
            styles: AnimationStyleMetadata | AnimationKeyframesSequenceMetadata |null = null
          ): AnimationAnimateMetadata;
複製程式碼
  • timings : duration delay easing

    它是一個字串,有三個引數:

    • duration : 動畫持續時間
    • delay : 動畫延遲幾秒後開始
    • easing : 動畫的緩和程度
      如果只有一個數字,那麼會被認為是 duration 的值。如:
    animate("200 ease-in", ...)
    // 被編譯成  duration=200, easing=ease-out
    
    animate("200 1000 ease-in", ...)
    // 被編譯成  duration=200, delay=1000, easing=ease-out
    複製程式碼
  • styles

    它可以是 style(樣式) 或 keyframes(關鍵幀),具體用法看下面詳細介紹。

    animate(timings, style({ background: "red" }))
    animate(timings, keyframes([
      style({ background: "blue", offset: 0.2})),  // 20%
      style({ background: "red", offset: 1}))   // 100%
    ])
    複製程式碼

style : 樣式設定

function style(
            tokens: `*` |
            {[key: string]: string | number} |
            Array<`*`|{[key: string]: string | number}>
         ): AnimationStyleMetadata;
複製程式碼

style宣告一個包含CSS屬性/樣式的鍵值對。

style({ width: 100, height: 0 })
複製程式碼

Auto-styles(自適應樣式): style值可以用 `*` 來表示,自動達到其原本的樣式,舉個例子你就明白作用了:

如果一個div它實際寬度是100px,高度為100px,讓它高度從0 到100px變化

<div class=`demo`></div>
...
.demo{
    width: 100px;
    height: 100px;
}
複製程式碼

這時候用 `*`來寫

 animations: [trigger(
      `autoHeight`,
      [
        state(`void`, style({height: `0px`})),
        state(`*`, style({height: `*`})),
        transition(`void => *`,  animate(500))
      ])],

複製程式碼

它就會在 500ms內 高度從 0 搭配100px。咦,似乎沒感覺到什麼作用…
在高度為動態獲取的值時候就看到其強大了:data為動態獲取的{ height: xxx }

<div class=`demo` [ngStyle]="{`height`:data.height}">
</div>
...
.demo{
    width: 100px;
}

複製程式碼
 animations: [trigger(
      `autoHeight`,
      [
        state(`void`, style({height: `0px`})),
        state(`*`, style({height: `*`})),
        transition(`void => *`,  animate(500))
      ])],

複製程式碼

這樣在 500ms 高度自動到達設定的值。

keyframes:關鍵幀

function keyframes(
             steps: AnimationStyleMetadata[]
         ): AnimationKeyframesSequenceMetadata;
複製程式碼

它的引數是一個style()的陣列,表示步驟。

animate("5s", keyframes([
  style({ backgroundColor: "red", offset: 0 }),  // 0%
  style({ backgroundColor: "blue", offset: 0.2 }), // 20%
  style({ backgroundColor: "orange", offset: 0.3 }), // 30%
  style({ backgroundColor: "black", offset: 1 })   // 100%
]))
複製程式碼

這裡的 offset 和css3 keyframes裡面的百分比一樣,是時間的偏移量。
offset: 0.2表示動畫在 20%的時候的樣式。

此外,如果沒有設定偏移量,那麼偏移量將自動計算

animate("5s", keyframes([
  style({ backgroundColor: "red" }) // offset = 0
  style({ backgroundColor: "blue" }) // offset = 0.33
  style({ backgroundColor: "orange" }) // offset = 0.66
  style({ backgroundColor: "black" }) // offset = 1
]))
複製程式碼

query: 選取元素,並新增動畫

function query(
            selector: string, 
            animation: AnimationMetadata | AnimationMetadata[], 
            options: AnimationQueryOptions | null = null
         ): AnimationQueryMetadata;
複製程式碼
  • selector : 要選取的元素,選取方式和原生的一樣。
  • animation : 要進行的動畫序列,一個或多個。
作用:

在處於動畫序列的元素內部查詢一個或多個元素,這些元素也會被加入當前動畫序列中,不過一般會重新寫一個陣列來重新定義選取元素的動畫序列。

用法:

1) 選取元素並可以限制數量
query()函式原始碼中使用了element.querySelectorAll因此他可以選取多個元素,所以我們在選取元素的時候可以加上一個 limit 來限制選取的數量。

// 在class為 demo的div裡找一個div,找到的是 demo1,如果 limit為2 的話找到的是 [demo1, demo2]。
template: `
  <div [@queryDemo] class=`demo`>
    <div class=`demo1`></div>
    <div class=`demo2`></div>
  </div>
`,
animations: [
   trigger(`queryDemo`, [
     transition(`void => *`, [
          query( `div`, animate(...), {limit: 1} )
     ])
   ]
]
複製程式碼

2) 報錯功能
預設情況下如果選取的元素找不到則 query()函式會報錯,設定optional選項為 true 則或忽略錯誤。

query(`.demo-not-be-there`, [
  animate(...),
  animate(...)
], { optional: true })
複製程式碼
選擇器的特殊值

query()函式裡面用偽選擇器可以選出特定的元素:

  • query(":enter")/query(":leave") : 選取新插入 / 移除的元素
  • query(":animating") : 選取所有正在進行動畫的元素
  • query("@triggerName") : 選取有特定觸發器的元素
  • query("@*") : 選取所有具有觸發器的元素
  • query(":self") : 把當前元素增加到動畫序列中

多個偽選擇器可以合在一起組成選擇器查詢字串:

query(`:self, .record:enter, .record:leave, @subTrigger`, [...])
複製程式碼
例子
@Component({
  selector: `inner`,
  template: `
    <div [@queryAnimation]="exp">
      <h1>Title</h1>
      <div class="content">
        Blah blah blah
      </div>
    </div>
  `,
  animations: [
   trigger(`queryAnimation`, [
     transition(`* => goAnimate`, [
       // 隱藏裡面的元素
       query(`h1`, style({ opacity: 0 })),
       query(`.content`, style({ opacity: 0 })),
 
       // 一個一個地執行裡面元素的動畫
       query(`h1`, animate(1000, style({ opacity: 1 })),
       query(`.content`, animate(1000, style({ opacity: 1 })),
     ])
   ])
 ]
})
class Cmp {
  exp = ``;
 
  goAnimate() {
    this.exp = `goAnimate`;
  }
}
複製程式碼

sequence : 序列

function sequence(
            steps: AnimationMetadata[], 
            options: AnimationOptions | null = null
         ): AnimationSequenceMetadata;
複製程式碼
  • steps : 動畫序列的步驟

它可以由樣式或動畫函式呼叫組成。對style()的呼叫將立即應用提供的樣式資料,而對animate()的呼叫將在它延遲時間後應用它的樣式資料。

  • options: 參見 AnimationOptions
// 這是一個動畫序列
sequence([
  style({ opacity: 0 })),
  animate("1s", { opacity: 1 }))
])
複製程式碼
那到底什麼是序列呢?

它是一個動畫列表[A,B,C],裡面的動畫挨個執行:執行完A再執行B,B執行完再執行C。
它可以應用在 grouptransition裡面,它只會在每個內部動畫步驟完成後再繼續執行下一條指令。

將一個陣列作為動畫資料傳遞到transition時,預設使用序列。如下面的[animate(500, style({...})), animate(500)]就是序列。

 animations: [trigger(
      `demo`,
      [
        state(`void`, style({...})),
        state(`*`, style({...})),
        transition(
            `void => *`, [animate(500, style({...})), animate(500)])
      ])],
複製程式碼
它和組(group)的異同:

都是動畫列表,序列是一個一個執行,組是並行執行。

group : 組

function group(
            steps: AnimationMetadata[],
            options: AnimationOptions | null = null
         ): AnimationGroupMetadata;
複製程式碼
  • steps : 動畫步驟資料
    它可以由樣式或動畫函式呼叫組成。在一個組中,每個樣式或動畫函式的呼叫都會立即同時執行,當然你可以使用關鍵幀或者動畫的延遲函式來延遲執行動畫。
  • options: 參見 AnimationOptions
group([
  animate("1s", { background: "black" }))
  animate("2s", { color: "white" }))
])
複製程式碼
什麼是組呢?

組是一個並行執行的動畫步驟列表。當存在很多樣式在不同時間段開始或結束動畫,我們需要對它統一進行管理的時候作用非常明顯。利用組我們可以輕易控制它們同時開始動畫或者同時結束動畫。
group函式既可以在序列(sequence)中使用,也可以在轉換(transition)中使用,它只會在所有內部動畫步驟完成後繼續執行下一條指令。

group官網介紹

AnimationOptions :動畫的可選項

import { AnimationOptions } from `@angular/animations`;
複製程式碼
interface AnimationOptions { 
  delay?: number|string
  params?: {[name: string]: any}
}
複製程式碼
  • delay: number|string
    動畫的延遲值。
  • params
    在啟動動畫時傳入引數,以更改樣式。
以下動畫函式可配置 AnimationOptions:
  • transition()
  • sequence()
  • group()
  • query()
  • animation()
  • useAnimation()
  • animateChild()
  • 使用AnimationBuilder服務構建的動畫
子介面
  • AnimateChildOptions
  • AnimationQueryOptions

例項: 比如現在想做一個動畫效果 : 點選按鈕 div 寬從 0 到100px。

  1. 新增動畫觸發器
// ts
import { trigger } from `@angular/core`;

@Component({
  templateUrl: `my-demo.html`,
  animations: [
    trigger( `growWidth`, [ // 這裡是定義的一些動畫效果] )
  ]
})

// html
<div [@growWidth]>...</div>
複製程式碼
  1. 再進行狀態(寬度)的改變
import { trigger, state, style } from `@angular/core`;

@Component({
  templateUrl: `my-demo.html`,
  animations: [
    trigger( `growWidth`, [ 
         state( `smWidth, void`, style({width: `0px`}) ),
         state( `lgWidth`, style({width: `100px`}) )
    ] )
  ]
})

複製程式碼

上面定義了兩個狀態,一個是寬度最小時候為0,一個寬度最大時候為 100px;
那麼有growWidth 動畫觸發器的 div 會完成這個寬度 0 到100px的動畫。

  1. 上面雖然完成了兩個不同狀態之間的改變,但是沒有過渡效果,用transition實現過渡。
import { trigger, state, transition, animate } from `@angular/core`;

@Component({
  templateUrl: `my-demo.html`,
  animations: [
    trigger( `growWidth`, [ 
         state( `smWidth, void`, style({width: `0px`}) ),
         state( `lgWidth`, style({width: `100px`}) ),
         transition(`smWidth => lgWidth`, animate(600)),
         transition(`lgWidth => smWidth`, animate(600)),
    ] )
  ]
})

複製程式碼

上面規定了從 smWidth 狀態到 lgWidth狀態需要0.6s的時間,所以可以看到div的寬度是慢慢變大的。

| 更多API用法更新於 github

相關文章