你已經(jīng)在簡介頁學習了 Angular 動畫的基礎知識。
本章將深入講解特殊的轉場狀態(tài),如 ?*
?(通配符)和 ?void
?,并說明這些特殊狀態(tài)如何作用于進入或離開視圖的元素。本章還探討了多重觸發(fā)器、動畫回調(diào),以及使用關鍵幀技術的序列動畫。
在 Angular 中,轉場狀態(tài)可以通過 ?state()
? 函數(shù)進行顯式定義,或使用預定義的 ?*
?(通配符)狀態(tài)和 ?void
?狀態(tài)。
星號 ?*
? 或者叫通配符可以匹配任何一個動畫狀態(tài)。它可用來定義那些不用在乎 HTML 元素的起始狀態(tài)或結束狀態(tài)的轉場動畫。
比如,一個 ?open => *
? 轉場可應用在當元素的狀態(tài)從 ?open
?變成任何其它狀態(tài)時。
下面是通配符狀態(tài)的另一個代碼范例,以及我們以前使用 ?open
?和 ?closed
?狀態(tài)的實例。但這次,對于每個狀態(tài)到狀態(tài)的轉換對,我們這次規(guī)定從任何狀態(tài)轉場到 ?closed
?狀態(tài)時要花 1 秒鐘,而從任何狀態(tài)轉場到 ?open
?狀態(tài)時要花 0.5 秒。
這讓我們可以添加新狀態(tài),而不必把它手動包含到每個單獨的轉場中。
animations: [
trigger('openClose', [
// ...
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('closed', style({
height: '100px',
opacity: 0.8,
backgroundColor: 'blue'
})),
transition('* => closed', [
animate('1s')
]),
transition('* => open', [
animate('0.5s')
]),
]),
],
使用雙向箭頭語法可以指定任意方向的狀態(tài)轉場。
transition('open <=> closed', [
animate('0.5s')
]),
在這個雙態(tài)按鈕的例子中,通配符不是很有用,因為只有兩種可能的狀態(tài):?open
?和 ?closed
?。一般而言,當一個特定狀態(tài)下的元素可能變更為多個潛在狀態(tài)時,通配符狀態(tài)會更好用。如果我們的按鈕可以從 ?open
?變成 ?closed
?或類似 ?inProgress
?的狀態(tài),則可以使用通配符狀態(tài)來減少所需的編碼量。
animations: [
trigger('openClose', [
// ...
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('closed', style({
height: '100px',
opacity: 0.8,
backgroundColor: 'blue'
})),
transition('open => closed', [
animate('1s')
]),
transition('closed => open', [
animate('0.5s')
]),
transition('* => closed', [
animate('1s')
]),
transition('* => open', [
animate('0.5s')
]),
transition('open <=> closed', [
animate('0.5s')
]),
transition ('* => open', [
animate ('1s',
style ({ opacity: '*' }),
),
]),
transition('* => *', [
animate('1s')
]),
當在任意兩個狀態(tài)之間切換時,?* => *
? 轉場都會生效。
轉場會按照其定義的順序進行匹配。因此,你可以在 ?* => *
? 轉場的前面定義其它轉場。比如,定義只針對 ?open => closed
? 的狀態(tài)變更或動畫,或 ?closed => open
?,而使用 ?* => *
? 作為匹配不上其它狀態(tài)對時的后備。
要這么做,只要把那些更特殊的轉場放在 ?* => *
? 前面就行了。
使用帶樣式的 ?*
? 通配符來告訴動畫使用當前的狀態(tài)值,并用它進行動畫處理。通配符是一個后備值,如果未在觸發(fā)器中聲明動畫狀態(tài),就會使用這個值。
transition ('* => open', [
animate ('1s',
style ({ opacity: '*' }),
),
]),
可以使用 ?void
?狀態(tài)來為進入或離開頁面的元素配置轉場。
可以在轉場中組合使用通配符和 ?void
?狀態(tài),以觸發(fā)那些進入和離開頁面的動畫:
* => void
? 轉場,而不管它離開前處于什么狀態(tài)void => *
? 轉場,而不管它進入時處于什么狀態(tài)*
? 會匹配任何狀態(tài) —— 包括 ?void
?本節(jié)介紹如何為進入和離開頁面的元素設置動畫。
添加一些新的行為:
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%)' }))
])
])
]
在上述代碼中,當 HTML 元素沒有附著在視圖中時,我們就會應用 ?void
?狀態(tài)。
?:enter
? 和 ?:leave
? 分別是 ?void => *
? 和 ?* => void
? 的別名。這些別名供多個動畫函數(shù)使用。
transition ( ':enter', [ … ] ); // alias for void => *
transition ( ':leave', [ … ] ); // alias for * => void
定位進入視圖的元素更難,因為它不在 DOM 中。因此,使用別名 ?:enter
? 和 ?:leave
? 來定位要從視圖中插入或刪除的 HTML 元素。
當任何 ?*ngIf
? 或 ?*ngFor
? 中的視圖放進頁面中時,會運行 ?:enter
? 轉場;當移除這些視圖時,就會運行 ?:leave
? 轉場。
注意:
進入/離開行為有時會令人困惑。作為經(jīng)驗法則,考慮到 Angular 添加到 DOM 的任何元素都會通過 ?:enter
? 轉換傳遞,但只有通過 Angular 直接從 DOM 刪除的元素會通過 ?:leave
? 轉換傳遞(例如,元素的視圖是從 DOM,因為其父級正在從 DOM 中刪除或應用程序的路由已更改,則元素將不會通過 ?:leave
? 轉換)。
本例子中有一個名叫 ?myInsertRemoveTrigger
?的觸發(fā)器,來表示進入和離開動畫。其 HTML 模板包含下列代碼。
<div @myInsertRemoveTrigger *ngIf="isShown" class="insert-remove-container">
<p>The box is inserted</p>
</div>
在組件文件中,?:enter
? 轉場會將初始透明度設置為 0,然后設置動畫,當該元素已經(jīng)插入視圖中之后,把這個透明度設置為 1。
trigger('myInsertRemoveTrigger', [
transition(':enter', [
style({ opacity: 0 }),
animate('100ms', style({ opacity: 1 })),
]),
transition(':leave', [
animate('100ms', style({ opacity: 0 }))
])
]),
請注意,此示例不需要使用?state()
?。
?transition()
? 函數(shù)還能接受額外的選擇器值:?:increment
? 和 ?:decrement
?。當數(shù)值增加或減小時,使用這些來啟動轉場。
trigger('filterAnimation', [
transition(':enter, * => 0, * => -1', []),
transition(':increment', [
query(':enter', [
style({ opacity: 0, width: 0 }),
stagger(50, [
animate('300ms ease-out', style({ opacity: 1, width: '*' })),
]),
], { optional: true })
]),
transition(':decrement', [
query(':leave', [
stagger(50, [
animate('300ms ease-out', style({ opacity: 0, width: 0 })),
]),
])
]),
]),
如果某個觸發(fā)器以邏輯型的值作為綁定值,那么就可以使用能與 ?true
?和 ?false
? 或 1 和 0 相比較的 ?transition()
? 表達式來匹配這個值。
<div [@openClose]="isOpen ? true : false" class="open-close-container">
</div>
在上述代碼片段中,HTML 模板將 ?<div>
? 元素綁定到名為 ?openClose
?的觸發(fā)器,其狀態(tài)表達式是 ?isOpen
?,可能的值為 ?true
?和 ?false
?。這種模式可以代替創(chuàng)建兩個命名狀態(tài) ?open
?和 ?close
?的方式。
在組件代碼中,?@Component
? 元數(shù)據(jù)下的 ?animations:
? 屬性中,當該狀態(tài)求值為 ?true
? 時(這里表示 "open"),相關 HTML 元素的高度值為通配符樣式 ?*
? 或某個默認值。在這種情況下,它會使用此元素開始動畫前的現(xiàn)有高度。當該元素是 "closed" 時,它的高度會從指定的高度運動到 0,這會讓它不可見。
animations: [
trigger('openClose', [
state('true', style({ height: '*' })),
state('false', style({ height: '0px' })),
transition('false <=> true', animate(500))
])
],
你可以為組件定義多個動畫觸發(fā)器并將這些動畫觸發(fā)器附著到不同的元素上,這些元素之間的父子關系會影響動畫的運行方式和時機。
每次在 Angular 中觸發(fā)動畫時,父動畫始終會優(yōu)先,而子動畫會被阻塞。為了運行子動畫,父動畫必須查詢出包含子動畫的每個元素,然后使用 ?animateChild()
? 函數(shù)來運行它們。
可以把一個名叫 ?@.disabled
? 的動畫控制綁定放在 HTML 元素上,以禁用該元素及其子元素上的動畫。當 ?@.disabled
? 綁定為 ?true
?時,就會禁止渲染所有動畫。
下面的代碼范例展示了如何使用此特性。
<div [@.disabled]="isDisabled">
<div [@childAnimation]="isOpen ? 'open' : 'closed'"
class="open-close-container">
<p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
</div>
@Component({
animations: [
trigger('childAnimation', [
// ...
]),
],
})
export class OpenCloseChildComponent {
isDisabled = false;
isOpen = false;
}
當 ?@.disabled
? 綁定為 ?true
?時,?@childAnimation
? 觸發(fā)器就不會啟動。
當 HTML 模板中的某個元素使用 ?@.disabled
? 禁止了動畫時,也會同時禁止其所有內(nèi)部元素的動畫。你無法有選擇的單獨禁用單個元素上的多個動畫。
不過,選擇性的子動畫仍然可以用如下方式之一在已禁用的父元素上運行:
query()
? 函數(shù)來收集 HTML 模板中位于禁止動畫區(qū)域內(nèi)部的元素。這些元素仍然可以播放動畫。
animateChild()
? 來播放它。要禁用 Angular 應用中的所有動畫,只要把 ?@.disabled
? 綁定放在頂層的 Angular 組件上即可。
@Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.css'],
animations: [
slideInAnimation
]
})
export class AppComponent {
@HostBinding('@.disabled')
public animationsDisabled = false;
}
注意:
禁用應用級的動畫在端到端(E2E)測試中是很有用的。
當動畫啟動和終止時,?trigger()
? 函數(shù)會發(fā)出一些回調(diào)。在下面的例子中,我們有一個包含 ?openClose
?觸發(fā)器的組件。
@Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
// ...
]),
],
templateUrl: 'open-close.component.html',
styleUrls: ['open-close.component.css']
})
export class OpenCloseComponent {
onAnimationEvent(event: AnimationEvent) {
}
}
在 HTML 模板中,動畫事件可以通過 ?$event
? 傳遞回來,比如 ?@triggerName.start
? 和 ?@triggerName.done
?,這里的 ?triggerName
?表示所使用的觸發(fā)器名字。在我們的例子中,?openClose
?觸發(fā)器將會是這樣的。
<div [@openClose]="isOpen ? 'open' : 'closed'"
(@openClose.start)="onAnimationEvent($event)"
(@openClose.done)="onAnimationEvent($event)"
class="open-close-container">
</div>
動畫回調(diào)的潛在用途之一,是用來覆蓋比較慢的 API 調(diào)用,比如查閱數(shù)據(jù)庫。比如,你可以建立一個 InProgress 按鈕,讓它擁有自己的循環(huán)動畫。當后端系統(tǒng)操作完成時,它會播放脈動效果或其它一些視覺動作。
然后,在當前動畫結束時,可以調(diào)用另一個動畫。比如,當 API 調(diào)用完成時,按鈕會從 ?inProgress
?狀態(tài)變成 ?closed
?狀態(tài)。
動畫可以影響最終用戶,讓他覺得操作更快 —— 雖然并沒有。因此,簡單的動畫是保持用戶滿意的一種經(jīng)濟有效的手段,而不必尋求提高服務器調(diào)用的速度或被迫補救那些你無法控制的情況,比如不可靠的網(wǎng)絡連接。
回調(diào)可以作為調(diào)試工具,比如與 ?console.warn()
? 結合使用,以便在瀏覽器的開發(fā)者控制臺中查看應用的進度。下列代碼片段為我們原始的雙態(tài)按鈕(?open
?與 ?closed
?)范例創(chuàng)建了控制臺輸出。
export class OpenCloseComponent {
onAnimationEvent(event: AnimationEvent) {
// openClose is trigger name in this example
console.warn(`Animation Trigger: ${event.triggerName}`);
// phaseName is "start" or "done"
console.warn(`Phase: ${event.phaseName}`);
// in our example, totalTime is 1000 (number of milliseconds in a second)
console.warn(`Total time: ${event.totalTime}`);
// in our example, fromState is either "open" or "closed"
console.warn(`From: ${event.fromState}`);
// in our example, toState either "open" or "closed"
console.warn(`To: ${event.toState}`);
// the HTML element itself, the button in this case
console.warn(`Element: ${event.element}`);
}
}
前一節(jié)是簡單的雙態(tài)轉場?,F(xiàn)在,我們要使用關鍵幀動畫創(chuàng)建一個具有多個順序執(zhí)行步驟的動畫。
Angular 的 ?keyframe()
? 函數(shù)類似于 CSS 中的關鍵幀。關鍵幀允許在單個時間段內(nèi)進行多種樣式更改。比如,我們的按鈕可以在單個的 2 秒時間段內(nèi)多次改變顏色,而不是漸隱掉。
這些更改顏色的代碼如下所示。
transition('* => active', [
animate('2s', keyframes([
style({ backgroundColor: 'blue' }),
style({ backgroundColor: 'red' }),
style({ backgroundColor: 'orange' })
]))
關鍵幀包括一個用來定義動畫中每個樣式何時開始更改的偏移(offset)屬性。偏移是個 0 到 1 之間的相對值,分別標記動畫的開始和結束時間,并且只要使用了它,就要同樣應用于這個關鍵幀的每個步驟。
定義關鍵幀的偏移量是可選的。如果省略它們,就會自動分配均勻間隔的偏移。比如,三個沒有預定義偏移的關鍵幀會分別使用 0、0.5、1 作為偏移。在上面的例子中,還可以為中間的轉場指定偏移量 0.8。代碼如下。
帶有指定偏移量的代碼如下。
transition('* => active', [
animate('2s', keyframes([
style({ backgroundColor: 'blue', offset: 0}),
style({ backgroundColor: 'red', offset: 0.8}),
style({ backgroundColor: '#754600', offset: 1.0})
])),
]),
transition('* => inactive', [
animate('2s', keyframes([
style({ backgroundColor: '#754600', offset: 0}),
style({ backgroundColor: 'red', offset: 0.2}),
style({ backgroundColor: 'blue', offset: 1.0})
]))
]),
你可以在單個動畫中組合使用 ?duration
?、?delay
?和 ?easing
?來定義關鍵幀。
通過在整個動畫中定義特定偏移處的樣式,可以使用關鍵幀在動畫中創(chuàng)建脈動效果。
下面是使用關鍵幀創(chuàng)建脈動效果的例子:
open
?和 ?closed
?狀態(tài)(包括其原始的高度、顏色和透明度)會在一秒鐘內(nèi)逐漸發(fā)生變化。
此動畫的代碼片段是這樣的。
trigger('openClose', [
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow'
})),
state('close', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green'
})),
// ...
transition('* => *', [
animate('1s', keyframes ( [
style({ opacity: 0.1, offset: 0.1 }),
style({ opacity: 0.6, offset: 0.2 }),
style({ opacity: 1, offset: 0.5 }),
style({ opacity: 0.2, offset: 0.7 })
]))
])
])
Angular 的動畫支持是基于 Web 動畫的,所以你可以動瀏覽器認為可動(animatable)的任意屬性。包括位置、大小、變形、顏色、邊框等。W3C 在 CSS 轉場頁也維護了一個可動屬性的列表。
對于帶有數(shù)值的位置屬性,可以把值作為字符串(別忘了帶引號)并使用適當?shù)暮缶Y來定義其單位:
'50px'
?'3em'
?'100%'
?你還可以用數(shù)字形式提供這個值(不帶單位),這種情況下,Angular 假設默認的單位是像素(?px
?)。把 50 像素表示為 ?50
?和 ?'50px'
? 是一樣的。
注意:
字符串形式的 ?"50"
? 是無效的。
有時你在運行之前并不知道某個樣式的屬性值。比如,元素的寬度和高度通常取決于其內(nèi)容和屏幕大小。在使用 CSS 動畫時,這些屬性通常會具有挑戰(zhàn)性(譯注:因為 CSS 動畫不支持自動確定寬高)。
這些情況下,你可以在 ?style()
? 中指定通配符 ?*
? 屬性,以便在運行期間計算該屬性的值,然后把它插入到動畫中。
下面的例子中有一個名叫 ?shrinkOut
?的觸發(fā)器,它會在 HTML 元素離開頁面時使用。該動畫會使用它離開之前的任意高度,并從該高度動畫到 0。
animations: [
trigger('shrinkOut', [
state('in', style({ height: '*' })),
transition('* => void', [
style({ height: '*' }),
animate(250, style({ height: 0 }))
])
])
]
Angular 中的 ?keyframes()
? 函數(shù)允許你在單個轉場中指定多個臨時樣式,并使用可選的 ?offset
?來定義動畫中每次樣式變化的發(fā)生時機。
更多建議: