@angular/animations 套件自 v20.2 起已棄用,該版本同時引入了新的 animate.enter 與 animate.leave 功能,讓你為應用程式加入動畫。運用這些新功能,你可以把所有以 @angular/animations 為基礎的動畫,改為純 CSS 或 JS 動畫函式庫。從應用程式移除 @angular/animations,能顯著縮小 JavaScript 套件包的大小。原生 CSS 動畫通常能提供更優異的效能,因為它們可以受益於硬體加速。本指南將帶你走過把程式碼從 @angular/animations 重構為原生 CSS 動畫的過程。
若你從未寫過任何原生 CSS 動畫,有許多優秀的指南可以帶你入門。以下列出幾篇:
MDN 的 CSS Animations 指南
W3Schools CSS3 Animations 指南
The Complete CSS Animations Tutorial
CSS Animation for Beginners
以及幾部影片:
Learn CSS Animation in 9 Minutes
Net Ninja CSS Animation Tutorial Playlist
先看看其中一些指南與教學,再回來繼續閱讀本指南。
就和 animations 套件一樣,你可以建立可在整個應用程式共用的可重複使用動畫。animations 套件的版本是讓你在共用的 TypeScript 檔案中使用 animation() 函式。原生 CSS 的版本與此類似,但放在共用的 CSS 檔案中。
export const sharedAnimation = animation([
style({
height: 0,
opacity: 1,
backgroundColor: 'red',
}),
animate('1s'),
]);
@keyframes sharedAnimation {
to {
height: 0;
opacity: 1;
background-color: 'red';
}
}
.animated-class {
animation: sharedAnimation 1s;
}
把 animated-class 類別加到元素上,就會觸發該元素的動畫。
animations 套件讓你能在元件內使用 state() 函式定義各種狀態。範例可能是 open 或 closed 狀態,定義中包含各自狀態的樣式。例如:
// ...
state(
'open',
style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow',
}),
),
同樣的行為也可以用原生方式達成:使用 CSS 類別,搭配關鍵影格動畫或轉場樣式。
.open {
height: '200px';
opacity: 1;
background-color: 'yellow';
transition: all 1s;
}
.closed {
height: '100px';
opacity: 0.8;
background-color: 'blue';
transition: all 1s;
}
觸發 open 或 closed 狀態的方法,是在元件中切換元素上的類別。你可以在我們的範本指南中找到做法範例。
範本指南中也有直接為樣式加入動畫的類似範例。
animations 套件的 animate() 函式允許提供時間資訊,例如持續時間、延遲與緩動。這可以用 CSS 原生達成,使用多個 CSS 屬性或簡寫屬性即可。
在 CSS 中為關鍵影格動畫指定 animation-duration、animation-delay 與 animation-timing-function,或改用 animation 簡寫屬性。
.example-element {
animation-duration: 1s;
animation-delay: 500ms;
animation-timing-function: ease-in-out;
}
.example-shorthand {
animation: exampleAnimation 1s ease-in-out 500ms;
}
同樣地,對於不使用 @keyframes 的動畫,你可以使用 transition-duration、transition-delay、transition-timing-function 以及 transition 簡寫屬性。
.example-element {
transition-duration: 1s;
transition-delay: 500ms;
transition-timing-function: ease-in-out;
transition-property: margin-right;
}
.example-shorthand {
transition: margin-right 1s ease-in-out 500ms;
}
animations 套件要求使用 trigger() 函式指定觸發器,並把所有狀態都巢狀放在其中。使用原生 CSS 時,這就不必要了。只要切換 CSS 樣式或類別,就能觸發動畫。當類別出現在元素上時,動畫就會發生。移除類別後,元素會還原為該元素所定義的 CSS。這讓完成相同動畫所需的程式碼顯著減少。以下是一個範例:
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style, keyframes} from '@angular/animations';
@Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
state(
'open',
style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow',
}),
),
state(
'closed',
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}),
]),
),
]),
]),
],
templateUrl: 'open-close.html',
styleUrl: 'open-close.css',
})
export class OpenClose {
isOpen = signal(false);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<nav>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
</nav>
<div [@openClose]="isOpen() ? 'open' : 'closed'" class="open-close-container">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
:host {
display: block;
margin-top: 1rem;
}
.open-close-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-open-close',
templateUrl: 'open-close.html',
styleUrls: ['open-close.css'],
})
export class OpenClose {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<h2>Open / Close Example</h2>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>
<div class="open-close-container" [class.open]="isOpen()">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
:host {
display: block;
margin-top: 1rem;
}
.open-close-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
font-weight: bold;
font-size: 20px;
height: 100px;
opacity: 0.8;
background: #3b82f6;
color: #ebebeb;
transition-property: height, opacity, background-color, color;
transition-duration: 1s;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
.open {
transition-duration: 0.5s;
height: 200px;
opacity: 1;
background: #475569;
color: #f9fafb;
}
animations 套件提供透過字串把你定義的狀態與轉場比對的能力。例如,從開啟到關閉的動畫會是 open => closed。你可以使用萬用字元把任何狀態與目標狀態比對,例如 * => closed,而 void 關鍵字可用於進入與退出狀態。例如:元素離開檢視時用 * => void,元素進入檢視時用 void => *。
用 CSS 直接製作動畫時,完全不需要這些狀態比對模式。你可以根據你在元素上設定的類別和/或樣式,管理要套用哪些轉場與 @keyframes 動畫。你也可以加入 @starting-style,控制元素剛進入 DOM 時的外觀。
animations 套件提供為歷來難以加入動畫的項目加入動畫的能力,例如把固定高度動畫化為 height: auto。現在用純 CSS 也能做到這一點。
import {Component, signal} from '@angular/core';
import {trigger, transition, state, animate, style} from '@angular/animations';
@Component({
selector: 'app-open-close',
animations: [
trigger('openClose', [
state('true', style({height: '*'})),
state('false', style({height: '0px'})),
transition('false <=> true', animate(1000)),
]),
],
templateUrl: 'auto-height.html',
styleUrl: 'auto-height.css',
})
export class AutoHeight {
isOpen = signal(false);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<h2>Auto Height Example</h2>
<button type="button" (click)="toggle()">Toggle Open/Close</button>
<div class="container" [@openClose]="isOpen() ? true : false">
<div class="content">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
</div>
.container {
display: block;
overflow: hidden;
}
.container .content {
padding: 20px;
margin-top: 1em;
font-weight: bold;
font-size: 20px;
background: #3b82f6;
color: #ebebeb;
}
你可以使用 CSS Grid 為自動高度加入動畫。
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-auto-height',
templateUrl: 'auto-height.html',
styleUrls: ['auto-height.css'],
})
export class AutoHeight {
isOpen = signal(true);
toggle() {
this.isOpen.update((isOpen) => !isOpen);
}
}
<h2>Auto Height Example</h2>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Open/Close</button>
<div class="container" [class.open]="isOpen()">
<div class="content">
<p>The box is now {{ isOpen() ? 'Open' : 'Closed' }}!</p>
</div>
</div>
.container {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 1s;
}
.container.open {
grid-template-rows: 1fr;
}
.container .content {
min-height: 0;
transition: visibility 1s;
padding: 0 20px;
visibility: hidden;
margin-top: 1em;
font-weight: bold;
font-size: 20px;
background: #3b82f6;
color: #ebebeb;
overflow: hidden;
}
.container.open .content {
visibility: visible;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
若你不需要顧慮支援所有瀏覽器,也可以看看 calc-size(),那是為自動高度加入動畫的真正解法。更多資訊請見 MDN 的文件與這篇教學。
animations 套件除了提供前面提到的進入與離開模式比對,還包含 :enter 與 :leave 的簡寫別名。
import {Component} from '@angular/core';
import {trigger, transition, animate, style} from '@angular/animations';
@Component({
selector: 'app-insert-remove',
animations: [
trigger('myInsertRemoveTrigger', [
transition(':enter', [style({opacity: 0}), animate('200ms', style({opacity: 1}))]),
transition(':leave', [animate('200ms', style({opacity: 0}))]),
]),
],
templateUrl: 'insert-remove.html',
styleUrls: ['insert-remove.css'],
})
export class InsertRemove {
isShown = false;
toggle() {
this.isShown = !this.isShown;
}
}
<h2>Insert/Remove</h2>
<nav>
<button type="button" (click)="toggle()">Toggle Insert/Remove</button>
</nav>
@if (isShown) {
<div @myInsertRemoveTrigger class="insert-remove-container">
<p>The box is inserted</p>
</div>
}
:host {
display: block;
}
.insert-remove-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px 20px 0px 20px;
color: #000000;
font-weight: bold;
font-size: 20px;
}
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-insert',
templateUrl: 'insert.html',
styleUrls: ['insert.css'],
})
export class Insert {
isShown = signal(false);
toggle() {
this.isShown.update((isShown) => !isShown);
}
}
<h2>Insert Element Example</h2>
<nav>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container" animate.enter="enter-animation">
<p>The box is inserted</p>
</div>
}
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px;
font-weight: bold;
font-size: 20px;
}
.insert-container p {
margin: 0;
}
.enter-animation {
animation: slide-fade 1s;
}
@keyframes slide-fade {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-remove',
templateUrl: 'remove.html',
styleUrls: ['remove.css'],
})
export class Remove {
isShown = signal(false);
toggle() {
this.isShown.update((isShown) => !isShown);
}
}
<h2>Remove Element Example</h2>
<nav>
<button type="button" class="toggle-btn" (click)="toggle()">Toggle Element</button>
</nav>
@if (isShown()) {
<div class="insert-container" animate.leave="deleting">
<p>The box is inserted</p>
</div>
}
:host {
display: block;
}
.insert-container {
border: 1px solid #dddddd;
margin-top: 1em;
padding: 20px;
font-weight: bold;
font-size: 20px;
opacity: 1;
transition: opacity 200ms ease-in;
@starting-style {
opacity: 0;
}
}
.insert-container p {
margin: 0;
}
.deleting {
opacity: 0;
transform: translateY(20px);
transition:
opacity 500ms ease-out,
transform 500ms ease-out;
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
更多關於 animate.enter 與 animate.leave 的資訊,請見進入與離開動畫指南。
除了前面提到的 :enter 與 :leave,還有 :increment 與 :decrement。你也可以透過加入與移除類別為它們加入動畫。與 animations 套件的內建別名不同,當值上升或下降時,不會自動套用類別。你可以以程式方式套用適當的類別。以下是一個範例:
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';
@Component({
selector: 'app-increment-decrement',
templateUrl: 'increment-decrement.html',
styleUrls: ['increment-decrement.css'],
animations: [
trigger('incrementAnimation', [
transition(':increment', [
animate('300ms ease-out', style({color: 'green', transform: 'scale(1.3, 1.2)'})),
]),
transition(':decrement', [
animate('300ms ease-out', style({color: 'red', transform: 'scale(0.8, 0.9)'})),
]),
]),
],
})
export class IncrementDecrement {
num = signal(0);
modify(n: number) {
this.num.update((v) => (v += n));
}
}
<h3>Increment and Decrement Example</h3>
<section>
<p [@incrementAnimation]="num()">Number {{ num() }}</p>
<div class="controls">
<button type="button" (click)="modify(1)">+</button>
<button type="button" (click)="modify(-1)">-</button>
</div>
</section>
:host {
display: block;
font-size: 32px;
margin: 20px;
text-align: center;
}
section {
border: 1px solid lightgray;
border-radius: 50px;
}
p {
display: inline-block;
margin: 2rem 0;
text-transform: uppercase;
}
.controls {
padding-bottom: 2rem;
}
button {
font: inherit;
border: 0;
background: lightgray;
width: 50px;
border-radius: 10px;
}
button + button {
margin-left: 10px;
}
import {Component, ElementRef, OnInit, signal, viewChild} from '@angular/core';
@Component({
selector: 'app-increment-decrement',
templateUrl: 'increment-decrement.html',
styleUrls: ['increment-decrement.css'],
})
export class IncrementDecrement implements OnInit {
num = signal(0);
el = viewChild<ElementRef<HTMLParagraphElement>>('el');
ngOnInit() {
this.el()?.nativeElement.addEventListener('animationend', (ev) => {
if (ev.animationName.endsWith('decrement') || ev.animationName.endsWith('increment')) {
this.animationFinished();
}
});
}
modify(n: number) {
const targetClass = n > 0 ? 'increment' : 'decrement';
this.num.update((v) => (v += n));
this.el()?.nativeElement.classList.add(targetClass);
}
animationFinished() {
this.el()?.nativeElement.classList.remove('increment', 'decrement');
}
ngOnDestroy() {
this.el()?.nativeElement.removeEventListener('animationend', this.animationFinished);
}
}
<h3>Increment and Decrement Example</h3>
<section>
<p #el>Number {{ num() }}</p>
<div class="controls">
<button type="button" (click)="modify(1)">+</button>
<button type="button" (click)="modify(-1)">-</button>
</div>
</section>
:host {
display: block;
font-size: 32px;
margin: 20px;
text-align: center;
}
section {
border: 1px solid lightgray;
border-radius: 50px;
}
p {
display: inline-block;
margin: 2rem 0;
text-transform: uppercase;
}
.increment {
animation: increment 300ms;
}
.decrement {
animation: decrement 300ms;
}
.controls {
padding-bottom: 2rem;
}
button {
font: inherit;
border: 0;
background: lightgray;
width: 50px;
border-radius: 10px;
}
button + button {
margin-left: 10px;
}
@keyframes increment {
33% {
color: green;
transform: scale(1.3, 1.2);
}
66% {
color: green;
transform: scale(1.2, 1.2);
}
100% {
transform: scale(1, 1);
}
}
@keyframes decrement {
33% {
color: red;
transform: scale(0.8, 0.9);
}
66% {
color: red;
transform: scale(0.9, 0.9);
}
100% {
transform: scale(1, 1);
}
}
與 animations 套件不同,當在指定元件內指定多個動畫時,沒有任何動畫會優先於其他動畫,也沒有任何東西會阻止動畫觸發。任何動畫的順序安排,都必須由你的 CSS 動畫定義來處理,使用 animation/transition 的延遲,和/或使用 animationend 或 transitionend 來處理加入下一個要動畫化的 CSS。
子元素動畫只會在同一個元件範本內觸發。在 @angular/animations 套件中,父動畫可以使用 query() 與 animateChild() 查詢並觸發巢狀子元件中的動畫。使用 animate.leave 與原生 CSS 動畫時,定義在巢狀子元件範本內的動畫,不會在父元件移除元素或檢視時觸發。只有同一個 Angular 元件範本內的巢狀動畫會執行。更多資訊請見進入與離開動畫指南。
使用原生 CSS 動畫時,若你想停用已指定的動畫,有多種做法。
- 建立一個自訂類別,強制把 animation 與 transition 設為
none。
.no-animation {
animation: none !important;
transition: none !important;
}
把這個類別套用到元素上,就能避免該元素觸發任何動畫。你也可以把作用範圍設定為整個 DOM 或 DOM 的某個區段,來強制這種行為。不過,這會讓動畫事件無法觸發。若你正在等待動畫事件來移除元素,這個做法就行不通。替代方案是把持續時間設為 1 毫秒。
使用 prefers-reduced-motion media query,確保偏好減少動畫的使用者不會看到動畫播放。
避免以程式方式加入動畫類別
animations 套件提供了回呼,讓你在動畫結束時想做某些事時使用。原生 CSS 動畫也有這些回呼。
OnAnimationStart
OnAnimationEnd
OnAnimationIteration
OnAnimationCancel
OnTransitionStart
OnTransitionRun
OnTransitionEnd
OnTransitionCancel
Web Animations API 還提供許多額外功能。請參閱文件,了解所有可用的動畫 API。
NOTE: 使用這些回呼時請注意事件冒泡問題。若你同時為子元素與父元素加入動畫,事件會從子元素向上冒泡到父元素。請考慮停止事件傳播,或查看事件中的更多細節,確認你回應的是想要的事件目標、而不是從子節點冒泡上來的事件。你可以檢查 animationname 屬性或正在轉場的屬性,確認處理的是正確的節點。
animations 套件內建了建立複雜序列的功能。這些序列在不用 animations 套件的情況下,也完全做得到。
在 animations 套件中,你可以使用 query() 函式依 CSS 類別名稱尋找特定元素,類似 document.querySelector()。在原生 CSS 動畫的世界裡,這就不必要了。取而代之的是,你可以使用 CSS 選擇器鎖定子類別,並套用任何想要的 transform 或 animation。
若要在範本內切換子節點的類別,你可以使用 class 與 style 繫結,在正確的時間點加入動畫。
stagger() 函式讓你能把清單中每個項目的動畫延遲一段指定時間,創造瀑布般的效果。你可以在原生 CSS 中利用 animation-delay 或 transition-delay 複製這種行為。以下範例示範這類 CSS 可能的寫法。
import {Component, HostBinding, signal} from '@angular/core';
import {trigger, transition, animate, style, query, stagger} from '@angular/animations';
@Component({
selector: 'app-stagger',
templateUrl: 'stagger.html',
styleUrls: ['stagger.css'],
animations: [
trigger('pageAnimations', [
transition(':enter', [
query('.item', [
style({opacity: 0, transform: 'translateY(-10px)'}),
stagger(200, [animate('500ms ease-in', style({opacity: 1, transform: 'none'}))]),
]),
]),
]),
],
})
export class Stagger {
@HostBinding('@pageAnimations')
items = [1, 2, 3];
}
<h2>Stagger Example</h2>
<ul class="items">
@for (item of items; track item) {
<li class="item">{{ item }}</li>
}
</ul>
.items {
list-style: none;
padding: 0;
margin: 0;
}
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-stagger',
templateUrl: './stagger.html',
styleUrls: ['stagger.css'],
})
export class Stagger {
show = signal(true);
items = [1, 2, 3];
refresh() {
this.show.set(false);
setTimeout(() => {
this.show.set(true);
}, 10);
}
}
<h1>Stagger Example</h1>
<button type="button" class="toggle-btn" (click)="refresh()">Refresh</button>
<div class="items-container">
@if (show()) {
<ul class="items">
@for (item of items; track $index) {
<li class="item" style="--index: {{ $index }}">{{ item }}</li>
}
</ul>
}
</div>
.items-container {
min-height: 4.5rem;
}
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
transition-delay: calc(200ms * var(--index));
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
animations 套件有一個 group() 函式,可以同時播放多個動畫。在 CSS 中,你可以完全控制動畫時間。若你定義了多個動畫,可以一次全部套用。
.target-element {
animation:
rotate 3s,
fade-in 2s;
}
在這個範例中,rotate 與 fade-in 動畫會同時觸發。
清單中的項目重新排序,運用前面說明的技巧即可開箱運作,不需要額外的特殊處理。@for 迴圈中的項目會被正確地移除再重新加入,進入動畫會透過 @starting-styles 觸發。或者,你也可以用 animate.enter 達成相同行為。使用 animate.leave 讓元素在被移除時播放動畫,如上例所示。
import {Component, signal} from '@angular/core';
import {trigger, transition, animate, query, style} from '@angular/animations';
@Component({
selector: 'app-reorder',
templateUrl: './reorder.html',
styleUrls: ['reorder.css'],
animations: [
trigger('itemAnimation', [
transition(':enter', [
style({opacity: 0, transform: 'translateX(-10px)'}),
animate('300ms', style({opacity: 1, transform: 'translateX(none)'})),
]),
transition(':leave', [
style({opacity: 1, transform: 'translateX(none)'}),
animate('300ms', style({opacity: 0, transform: 'translateX(-10px)'})),
]),
]),
],
})
export class Reorder {
show = signal(true);
items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];
randomize() {
const randItems = [...this.items];
const newItems = [];
for (let i of this.items) {
const max: number = this.items.length - newItems.length;
const randNum = Math.floor(Math.random() * max);
newItems.push(...randItems.splice(randNum, 1));
}
this.items = newItems;
}
}
<h1>Reordering List Example</h1>
<button type="button" (click)="randomize()">Randomize</button>
<ul class="items">
@for (item of items; track item) {
<li @itemAnimation class="item">{{ item }}</li>
}
</ul>
.items {
list-style: none;
padding: 0;
margin: 0;
}
import {Component, signal} from '@angular/core';
@Component({
selector: 'app-reorder',
templateUrl: './reorder.html',
styleUrls: ['reorder.css'],
})
export class Reorder {
show = signal(true);
items = ['stuff', 'things', 'cheese', 'paper', 'scissors', 'rock'];
randomize() {
const randItems = [...this.items];
const newItems = [];
for (let i of this.items) {
const max: number = this.items.length - newItems.length;
const randNum = Math.floor(Math.random() * max);
newItems.push(...randItems.splice(randNum, 1));
}
this.items = newItems;
}
}
<h1>Reordering List Example</h1>
<button type="button" class="toggle-btn" (click)="randomize()">Randomize</button>
<ul class="items">
@for (item of items; track item) {
<li class="item" animate.leave="fade">{{ item }}</li>
}
</ul>
.items {
list-style: none;
padding: 0;
margin: 0;
}
.items .item {
transition-property: opacity, transform;
transition-duration: 500ms;
@starting-style {
opacity: 0;
transform: translateX(-10px);
}
}
.items .item.fade {
animation: fade-out 500ms;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.toggle-btn {
background: transparent;
border: 1px solid var(--primary-contrast, black);
color: var(--primary-contrast, black);
padding: 10px 24px;
border-radius: 8px;
cursor: pointer;
}
AnimationPlayer 類別讓你存取動畫,透過程式碼做更進階的事,例如暫停、播放、重新開始與結束動畫。這些事全都可以原生處理。
你可以使用 Element.getAnimations() 直接從元素取得動畫。它會回傳該元素上每一個 Animation 組成的陣列。你可以用 Animation API 做到比 animations 套件的 AnimationPlayer 更多的事。由此你可以 cancel()、play()、pause()、reverse() 等等。這個原生 API 應該能提供你控制動畫所需的一切。
你可以使用檢視轉場為路由之間加入動畫。開始使用請見路由轉場動畫指南。