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.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.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.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.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 HeroListPage 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アニメーションについてさらに詳しく
以下の項目にも興味があるかもしれません: