IMPORTANT: @angular/animations パッケージは現在非推奨です。Angularチームは、新しく書くコードのアニメーションには animate.enter と animate.leave を使ったネイティブCSSの利用を推奨します。詳しくは、新しい enter と leave のアニメーションガイドを参照してください。また、アプリで純粋なCSSアニメーションへの移行を始める方法については、AngularのAnimationsパッケージからの移行も参照してください。
ここまで、単一のHTML要素のシンプルなアニメーションを学んできました。 Angularでは、ページに出入りする要素のグリッド全体やリスト全体など、連携したシーケンスもアニメーション化できます。 複数のアニメーションを並行して実行することも、個別のアニメーションを順番に実行することもできます。
複雑なアニメーションシーケンスを制御する関数は次のとおりです:
| 関数 | 詳細 |
|---|---|
query() |
内側のHTML要素を1つ以上見つけます。 |
stagger() |
複数要素のアニメーションに段階的な遅延を適用します。 |
group() |
複数のアニメーションステップを並行して実行します。 |
sequence() |
アニメーションステップを順番に実行します。 |
query() 関数
多くの複雑なアニメーションは、query() 関数で子要素を見つけ、それらにアニメーションを適用することに依存します。基本的な例は次のとおりです:
| 例 | 詳細 |
|---|---|
query() の後に animate() |
単純なHTML要素をクエリし、直接アニメーションを適用するために使用します。 |
query() の後に animateChild() |
それ自体にアニメーションメタデータが適用された子要素をクエリし、そのアニメーションをトリガーします(そうしないと、現在の要素/親要素のアニメーションによってブロックされてしまいます)。 |
query() の最初の引数は CSSセレクター 文字列で、次のAngular固有のトークンも含められます:
| トークン | 詳細 |
|---|---|
:enter :leave |
enter/leaveする要素用。 |
:animating |
現在アニメーション中の要素用。 |
@* @triggerName |
任意のトリガー、または特定のトリガーを持つ要素用。 |
:self |
アニメーション対象の要素自身。 |
enter と leave する要素
すべての子要素が実際に enter/leaveする要素として扱われるわけではありません。これは直感に反して混乱しやすい場合があります。詳しくはqueryのAPIドキュメントを参照してください。
また、アニメーションの例(アニメーションの導入セクションで紹介します)のQueryingタブでも、この挙動を図で確認できます。
query() と stagger() 関数を使って複数の要素をアニメーション化する
query() で子要素をクエリしたあと、stagger() 関数を使うと、アニメーション化される各項目の間に時間差を定義でき、要素を順番に遅らせてアニメーションできます。
次の例は、query() と stagger() 関数を使って、(ヒーローの)リストに要素を追加するときに、上から下へ少しずつ遅らせながら順番にアニメーションする方法を示します。
query()を使って、特定の条件を満たしてページに入ってくる要素を探します。それぞれの要素に対して、
style()を使い、同じ初期スタイルを設定します。 透明にし、transformを使って位置をずらしておくことで、所定の位置にスライドして入ってくるようにします。stagger()を使って各アニメーションを30ミリ秒ずつ遅らせます。カスタム定義のイージングカーブを使って各要素を0.5秒かけてアニメーションし、フェードインと
transformの解除を同時に行います。
hero-list-page.component.ts
animations: [ trigger('pageAnimations', [ transition(':enter', [ query('.hero', [ style({opacity: 0, transform: 'translateY(-100px)'}), stagger(30, [ animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({opacity: 1, transform: 'none'})), ]), ]), ]), ]),
group() 関数を使った並行アニメーション
ここまで、連続する各アニメーションの間に遅延を入れる方法を見てきました。
しかし、並行して実行されるアニメーションを設定したい場合もあるでしょう。
たとえば、同じ要素の2つのCSSプロパティをアニメーションしたいが、それぞれで異なる easing 関数を使いたい場合があります。
そのような場合は、アニメーションの group() 関数を使用できます。
HELPFUL: group() 関数は、アニメーション化される要素ではなく、アニメーションの ステップ をグループ化するために使います。
次の例は、:enter と :leave の両方で group() を使用し、2つの異なるタイミング設定を適用します。これにより、同じ要素に2つの独立したアニメーションを並行して適用できます。
hero-list-groups.component.ts (excerpt)
animations: [ trigger('flyInOut', [ state( 'in', style({ width: '*', transform: 'translateX(0)', opacity: 1, }), ), transition(':enter', [ style({width: 10, transform: 'translateX(50px)', opacity: 0}), group([ animate( '0.3s 0.1s ease', style({ transform: 'translateX(0)', width: '*', }), ), animate( '0.3s ease', style({ opacity: 1, }), ), ]), ]), transition(':leave', [ group([ animate( '0.3s ease', style({ transform: 'translateX(50px)', width: 10, }), ), animate( '0.3s 0.2s ease', style({ opacity: 0, }), ), ]), ]), ]), ],
順次アニメーションと並行アニメーション
複雑なアニメーションでは、同時に多くのことが起きえます。
では、複数のアニメーションが1つずつ順番に実行されるアニメーションを作りたい場合はどうでしょうか?先ほどは group() を使って、複数のアニメーションを同時に並行実行しました。
別の関数 sequence() を使うと、同じアニメーションを1つずつ順番に実行できます。
sequence() の中では、アニメーションステップは style() または animate() 関数呼び出しで構成されます。
フィルターアニメーションの例
例のページにある別のアニメーションも見てみましょう。
Filter/Staggerタブで、Search Heroes テキストボックスに Magnet や tornado などの文字を入力します。
フィルターは入力に合わせてリアルタイムに動作します。 文字を入力するごとにフィルターが徐々に厳しくなり、要素がページから消えていきます。 フィルターボックスの文字を削除すると、ヒーローのリストが徐々にページに再表示されます。
HTMLテンプレートには filterAnimation というトリガーが含まれます。
hero-list-page.component.html
<label for="search">Search heroes: </label><input type="text" id="search" #criteria (input)="updateCriteria(criteria.value)" placeholder="Search heroes"/><ul class="heroes" [@filterAnimation]="heroesTotal"> @for (hero of heroes; track hero) { <li class="hero"> <div class="inner"> <span class="badge">{{ hero.id }}</span> <span class="name">{{ hero.name }}</span> </div> </li> }</ul>
コンポーネントのデコレーターにある filterAnimation には3つのトランジションが含まれます。
hero-list-page.component.ts
@Component({ animations: [ 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}))])]), ]), ]), ],})export class HeroListPageComponent implements OnInit { heroesTotal = -1; get heroes() { return this._heroes; } private _heroes: Hero[] = []; ngOnInit() { this._heroes = HEROES; } updateCriteria(criteria: string) { criteria = criteria ? criteria.trim() : ''; this._heroes = HEROES.filter((hero) => hero.name.toLowerCase().includes(criteria.toLowerCase()), ); const newTotal = this.heroes.length; if (this.heroesTotal !== newTotal) { this.heroesTotal = newTotal; } else if (!criteria) { this.heroesTotal = -1; } }}
この例のコードは次のタスクを実行します:
- ユーザーがこのページを初めて開いたり移動してきたりしたときはアニメーションをスキップします(フィルターアニメーションは既に存在する要素を絞り込むものであるため、DOM内に既に存在する要素に対してのみ動作します)。
- 検索入力の値に基づいてヒーローをフィルタリングします。
変更のたびに次の処理を行います:
DOMから離脱する要素は、不透明度と幅を0に設定して非表示にします。
DOMに入ってくる要素を300ミリ秒かけてアニメーションします。 アニメーション中は、要素が既定の幅と不透明度になります。
DOMに入ってくる要素/離脱する要素が複数ある場合は、ページ上部から順に、各要素の間に50ミリ秒の遅延を入れてアニメーションをずらします。
並び替えられるリストの項目をアニメーション化する
Angularは既定で *ngFor のリスト項目を正しくアニメーション化しますが、並び順が変わると正しくアニメーションできなくなります。
これは、どの要素がどれなのかを追跡できなくなり、アニメーションが壊れてしまうためです。
こうした要素をAngularが追跡できるようにする唯一の方法は、NgForOf ディレクティブに TrackByFunction を割り当てることです。
これにより、Angularは常にどの要素がどれなのかを把握できるため、常に正しい要素に正しいアニメーションを適用できます。
IMPORTANT: 実行時に並び順が変わる可能性のある *ngFor リストの項目をアニメーション化する必要がある場合は、常に TrackByFunction を使用してください。
アニメーションとコンポーネントのビューカプセル化
AngularのアニメーションはコンポーネントのDOM構造に基づいており、直接 ビューカプセル化 を考慮しません。つまり、ViewEncapsulation.Emulated を使用するコンポーネントは、ViewEncapsulation.None を使用している場合とまったく同じように振る舞います(ViewEncapsulation.ShadowDom と ViewEncapsulation.ExperimentalIsolatedShadowDom は、後述するように異なる挙動になります)。
たとえば、(このガイドの残りでも登場する)query() 関数を、エミュレートされたビューカプセル化を使用しているコンポーネントツリーの最上部に適用すると、そのクエリはツリーのどの深さにあるDOM要素も特定でき(その結果アニメーション化でき)ます。
一方、ViewEncapsulation.ShadowDom と ViewEncapsulation.ExperimentalIsolatedShadowDom は、DOM要素を ShadowRoot 要素の内側に「隠す」ことでコンポーネントのDOM構造を変更します。こうしたDOM操作は、アニメーションの実装が単純なDOM構造に依存し、ShadowRoot 要素を考慮しないため、一部のアニメーションが正しく動作しない原因になります。そのため、ShadowDomのビューカプセル化を使用するコンポーネントを含むビューにアニメーションを適用することは避けることを推奨します。
アニメーションシーケンスのまとめ
複数要素をアニメーション化するAngularの関数は、まず query() で内側の要素を見つけることから始まります。たとえば、<div> 内のすべての画像を収集する場合です。
残りの関数である stagger()、group()、sequence() では、カスケード(段階的な遅延)を適用したり、複数のアニメーションステップをどのように適用するかを制御したりできます。
Angularアニメーションについてさらに詳しく
以下の項目にも興味があるかもしれません: