拡張エコシステム
レガシーアニメーション

複雑なアニメーションシーケンス

IMPORTANT: @angular/animations パッケージは現在非推奨です。Angularチームは、新しく書くコードのアニメーションには animate.enteranimate.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() 関数呼び出しで構成されます。

  • style() を使って、指定したスタイルデータを即座に適用します。
  • animate() を使って、指定した時間間隔にわたってスタイルデータを適用します。

フィルターアニメーションの例

例のページにある別のアニメーションも見てみましょう。 Filter/Staggerタブで、Search Heroes テキストボックスに Magnettornado などの文字を入力します。

フィルターは入力に合わせてリアルタイムに動作します。 文字を入力するごとにフィルターが徐々に厳しくなり、要素がページから消えていきます。 フィルターボックスの文字を削除すると、ヒーローのリストが徐々にページに再表示されます。

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.ShadowDomViewEncapsulation.ExperimentalIsolatedShadowDom は、後述するように異なる挙動になります)。

たとえば、(このガイドの残りでも登場する)query() 関数を、エミュレートされたビューカプセル化を使用しているコンポーネントツリーの最上部に適用すると、そのクエリはツリーのどの深さにあるDOM要素も特定でき(その結果アニメーション化でき)ます。

一方、ViewEncapsulation.ShadowDomViewEncapsulation.ExperimentalIsolatedShadowDom は、DOM要素を ShadowRoot 要素の内側に「隠す」ことでコンポーネントのDOM構造を変更します。こうしたDOM操作は、アニメーションの実装が単純なDOM構造に依存し、ShadowRoot 要素を考慮しないため、一部のアニメーションが正しく動作しない原因になります。そのため、ShadowDomのビューカプセル化を使用するコンポーネントを含むビューにアニメーションを適用することは避けることを推奨します。

アニメーションシーケンスのまとめ

複数要素をアニメーション化するAngularの関数は、まず query() で内側の要素を見つけることから始まります。たとえば、<div> 内のすべての画像を収集する場合です。 残りの関数である stagger()group()sequence() では、カスケード(段階的な遅延)を適用したり、複数のアニメーションステップをどのように適用するかを制御したりできます。

Angularアニメーションについてさらに詳しく

以下の項目にも興味があるかもしれません: