角度アプリケーションでのアニメーション





何らかの形式のアニメーションがなければ、真面目なアプリケーションはできません。 アニメーションは、アプリケーションをより現代的で美しく、多くの場合より理解しやすくし、アプリケーション内の空間の方向を改善します。 フィードバックがないと、アイテムをクリックしたときに何が起こったのかを理解するのが難しい場合があります。 以前は、必要に応じて、アプリケーションにアニメーションを追加し、CSSアニメーションを使用しましたが、一般にほぼ満足でした。







製品がAngular 2+に移行した後、Angularはアニメーションを記述する独自のメカニズムを提供するという事実に直面しました。 AngularはDOMトランザクションを完全に所有しているため、彼はアニメーションの説明を簡略化でき、CSSアニメーションを放棄することにしました。 そして、一般的に、それが何をもたらしたのかを見るのは興味深いものでした。 プロジェクト開発のほぼ1年間、私たちはCSSアニメーションに切り替えませんでした。Angularアニメーションで十分に成功できると言えます。 この記事では、Angular 2+のプロジェクトでアニメーションを使用する方法と、公式ガイドに記載されている何らかの理由でまだ機能していない機能について説明します。







アニメーション設定



Angularのアニメーションは、CSSアニメーションのパフォーマンスを維持しながらJSの柔軟性を提供する標準であるWeb Animations APIに基づいています。 このAPIの詳細については、 こちらをご覧ください 。 この標準をサポートするブラウザでは、アニメーションはCSSアニメーションと同じメカニズムを使用します。つまり、パフォーマンスが低下することはありません。 他のブラウザでは、 polyfileを使用する必要があります。 Angularの6番目のバージョンから、AnimationBuilderを直接使用しない場合(これはほとんど必要ありません)、polyfilを有効にする必要はありません-Angularは、サポートされていないブラウザーでCSSアニメーションの使用に切り替えます。







アニメーションの実験では、Angularの機能のみを使用して、単純なToDoアプリケーションを作成し、それに必要なアニメーションを追加します。 リポジトリで完全なアプリケーションコードを確認できます 。ここでは、アニメーションに直接関連する部分のみを示します。







アニメーションの処理を開始する前に、プロジェクトにアニメーションメカニズムを使用する機能を追加する必要があります。 これを行うには、プロジェクトにBrowserAnimationsModuleを含めるだけで、すべてのアニメーション機能が利用可能になります。 合計で、Angularにはアニメーションに関連する2つのモジュールBrowserAnimationsModuleNoopAnimationsModuleがあります。 コンポーネントをテストするときにアニメーションを無効にするには、2番目が必要です。







Angularでのアニメーションの基本的な使用



そのため、ToDoアプリケーションでは、次のアニメーションが必要です。









これがどのように見えるかです:

















何をアニメーション化するかは明らかです。次に、その方法を見てみましょう。 すべてのAngularアニメーションは、コンポーネントのメタデータに記述されています。 TodoItemコンポーネントをアニメーション化し、アニメーションを追加し始めます。 Angularは、アニメーションを作成するために、アニメーションモジュールからインポートする必要があるいくつかのエンティティ(トリガー、状態、スタイル、アニメーション、遷移、キーフレーム)を使用します。 アニメーションを追加する前に、それぞれが必要な理由を見てみましょう。 もちろん、すべての情報はAngulyarのドキュメントにありますが、基本的な概念がなければ、より複雑な状況に進むことはできません。







トリガー -アニメーションをコンポーネントまたはコンポーネント内のDOM要素にバインドできます。 トリガー名を指定することに加えて、すべてのアニメーション状態を指定し、異なる状態間の遷移を記述することもできます。 コンポーネントのメタデータにトリガーを追加した後、コンポーネントまたはアニメーション化されるテンプレートの要素にトリガーをバインドする必要があります。 これがどのように行われるかについては、後で検討します。 通常、トリガーの作成は次のとおりです。







@Component({ selector: 'my-component', templateUrl: 'my-component-tpl.html', animations: [ trigger("myAnimationTrigger", [ state(...), state(...), transition(...), transition(...) ]) ] }) class MyComponent { }
      
      





state-アニメーションで使用されるコンポーネントの状態を設定し、状態の名前とこの状態のスタイルのセットを指定できます。 実際、Angularのアニメーション全体は有限状態マシンであり、ある状態から別の状態への遷移があります。







 @Component({ selector: 'my-component', templateUrl: 'my-component-tpl.html', animations: [ trigger("myAnimationTrigger", [ state('collapsed', style({ height: '0px', color: 'maroon', borderColor: 'maroon' })), state('expanded', style({ height: '*', borderColor: 'green', color: 'green' })) ]) ] }) class MyComponent { }
      
      





状態パラメーターは、この状態にある要素に適用されることを理解する必要があります。







style-アニメーションまたはstateで指定されたコンポーネントの状態で使用されるCSSパラメーターとその値のリストを記述することができます。 ブラウザでアニメーション化されていると見なされるパラメータのみをアニメーション化できます。 スタイルの使用方法の例は、上記の状態関数の説明で見ました。 しかし、先に進む前に、CSSパラメーター値を設定する際の1つの重要なポイントについて説明します。







アプリケーションの実行中にパラメーターの値がわからない場合があります。たとえば、要素の高さや幅などです。 この場合、CSSを使用してこのようなパラメーターの変更をアニメートするのはかなり困難ですが、Angularでは非常に簡単に解決されます。パラメーター値として「*」を使用する必要があります。 次に、AngularはDOMから値を取得します。







アニメーションアニメーションの主な機能です。 それを使用する場合、アニメーション中に変化するタイミングパラメータおよび/またはスタイルパラメータを設定する必要があります。 重要な点は、animateで指定されたスタイルオプションがアニメーションのときにのみアクティブになることです。 しかし、それが終了すると、当然、アニメーションの終了時にコンポーネントが状態によって記述された状態のいずれかにない限り、DOMの要素の状態によって指示された状態に戻ります。 アニメーションのタイミングオプションは、CSSアニメーションでおなじみのさまざまな値を取ることができます。







 animate(500, style(...)) animate("1s", style(...)) animate("100ms 0.5s", style(...)) animate("5s ease", style(...)) animate("5s 10ms cubic-bezier(.17,.67,.88,.1)", style(...))
      
      





transition-アニメーション要素の状態間の遷移のシーケンスを記述することができます。 アニメーションの開始時に決定する最初のパラメーター。 次に、アニメーションとスタイルを使用してアニメーションオプションを指定できます。 アニメーションを開始するためのパラメータを決定するには、次のオプションを使用できます。









また、アニメーションを開始するためのパラメータとして、fromStateフィールドとtoStateフィールドがあるアニメーションパラメータの転送先の関数を指定できます。 これらのフィールドの値に基づいて、アニメーションを実行するかどうかを決定できます。 関数がtrueを返す場合、アニメーションが開始されます。







Angularアニメーションエンジンには、トランジションを設定するためのいくつかのエイリアスがあります:入力/退出およびインクリメント/デクリメント。 実際、最初のペアはオプション* => voidおよびvoid => *に似ています。2番目のペアでは、トリガーの値が1つ減少または増加したときにアニメーションを開始できます。 これは、たとえば、イメージスライダーコンポーネントを実装する場合に便利です。現在のイメージのインデックスを変更することにより、コンポーネントのアニメーションを制御できます。







デフォルトでは、トランザクションで指定されたすべてのアニメーションが順番に実行されます。 並列アニメーションを実行する必要がある場合は、グループ関数を使用してアニメーションをラップする必要があります。







 group([ animate("1s", { background: "black" })) animate("2s", { color: "white" })) ])
      
      





アニメーションを順番に実行するシーケンス関数もありますが、デフォルトでは暗黙的に指定されているため、通常は使用されません。







キーフレーム -アニメーション時間のさまざまな段階でのアニメーションの動作を決定できます。 これを行うには、0〜1のスケールを使用し、この期間のどの段階でアニメーション要素のスタイルをどのように表示するかを指定できます。 たとえば、この方法で、要素のバウンスアニメーションを実装できます。







 transition('* => bouncing', [ animate('300ms ease-in', keyframes([ style({transform: 'translate3d(0,0,0)', offset: 0}), style({transform: 'translate3d(0,-10px,0)', offset: 0.5}), style({transform: 'translate3d(0,0,0)', offset: 1}) ])) ])
      
      





そこで、Angularの基本的なアニメーション機能を調べました。 ただし、ToDoリストのアニメーションを開始する前に、アニメーションをコンポーネントに接続する方法と、ある状態から別の状態へのコンポーネントの遷移を追跡する方法という2つのポイントを考慮する必要があります。







アニメーションをコンポーネントに接続するには、2つの方法があります。コンポーネントテンプレートの要素にアニメーションをハングアップするか、コンポーネント自体にアニメーションをハングアップします。 テンプレート内の要素にアニメーションを掛けるには、要素にアニメーショントリガーを持つ属性を追加し、目的の状態を渡します。







 <div [@myAnimationTrigger]="myStatusExp">...</div>
      
      





この例では、変数myStatusExpがコンポーネントで定義されており、変数が変更されると、必要な遷移が遷移で指定されている場合にアニメーションが開始されます。







コンポーネント自体にアニメーションをスローする必要がある場合は、これに@HostBindingデコレーターを使用できます。







 class MyComponent { @HostBinding('@myAnimationTrigger') public myStatusExp; }
      
      





最後に考慮する必要があるのは、コンポーネントのアニメーションの変更を追跡する方法です。 これを行うには、アニメーショントリガー、開始アニメーションおよび停止アニメーションでハンドラーをハングさせることができます。







 <todo-item *ngFor="let item of items" (@myAnimationTrigger.start)="animationStarted($event)" (@myAnimationTrigger.done)="animationDone($event)" [@myAnimationTrigger]="myStatusExp"> </todo-item>
      
      





その結果、Angularはアニメーションの開始または終了時にハンドラーを呼び出し、そこに次の内容のAnimationEventを渡します。







 interface AnimationEvent { fromState: string toState: string totalTime: number phaseName: string element: any triggerName: string disabled: boolean }
      
      





ご覧のとおり、イベントにはアニメーションに関する多くのデータが含まれています。 通常、アニメーション終了サブスクリプションは、DOMからモーダルウィンドウを削除するなどのアクションを実行するのに役立ちます。







そこで、Angularでアニメーションを記述する基本的な方法を検討しました。 次に、コンポーネントのメタデータにアニメーションを追加しましょう。







 @Component({ ... animations: [ trigger('stateAnimation', [ state('incomplete', style({ 'color': 'black', 'text-decoration': 'none' })), state('complete', style({ 'color': '#d9d9d9', 'text-decoration': 'line-through' })), transition('incomplete => complete', [ style({ 'text-decoration': 'line-through' }), animate('0.2s') ]), transition('complete => incomplete', [ style({ 'text-decoration': 'none' }), animate('0.2s') ]) ]), trigger('todoAnimation', [ transition(':enter', [ style({ height: 0 }), animate('0.3s ease-in', style({ height: '*' })) ]), transition(':leave', [ animate('0.3s ease-out', style({ transform: 'scale(0)' })) ]), ]) ] ... }) export class TodoItemComponent { ... @HostBinding('@todoAnimation') true; @HostBinding('@stateAnimation') get state() { return this.todo.completed ? 'complete' : 'incomplete'; } ... }
      
      





完全なコードは、ベースアニメーションブランチのリポジトリで確認できます。







アニメーションコードの機能を見てみましょう。 2つのアニメーショントリガーを作成します。1つはタスクの状態をアニメーション化するため(stateAnimation)、もう1つはリストにタスクを追加または削除するアニメーション用(todoAnimation)です。







2番目のトリガーから始めましょう。 その中で、コンポーネントの2つの状態遷移を定義します。 DOMに要素が表示されたら、要素の初期の高さを0に設定し、高さの変化を要素の内容によって決定される値にアニメーション化します。 DOMから要素を削除するとき、スケール変換を1のランタイム状態から0の最終状態に適用します。すべてが非常に簡単です。







では、最初のトリガーを見てみましょう。 その中で、最初に2つのコンポーネント状態(不完全と完全)を定義し、これらの状態のスタイルを指定します。 上記で述べたように、これらのスタイルは、コンポーネントが状態にある間、コンポーネントでアクティブになります。 次に、これらの状態の間に2つのトランジションを設定します。最初にコンポーネントにスタイルを適用し(変更されたスタイルの一部がすぐに表示されるように)、残りのパラメーターをアニメーション化します。







そのため、アプリケーションに必要なアニメーションを追加しましたが、少し遊んだ後、問題があることに気付きました:ユーザーがタスクリストで何らかのアクションを実行したときにのみアニメーションを動作させたいが、タスクリストフィルターが変更されたときにも動作するリストの最初の入力時にタスクを実行します。 これは、フィルターが変更されると、ngForディレクティブがリストを再構築するためです。

















少し振り返ってみると、タスクのリストを変更している間に、何らかの方法でしばらくアニメーションをオフにする必要があることを理解し、Angularはそのような機会を与えてくれます。 これを行うには、[@ .disabled]属性をTodoListコンポーネントテンプレートに追加します。







 <ul class="todo-list" [@.disabled]="disableAnimation"> <app-todo-item *ngFor="let todo of todos; trackBy: trackById" [todo]="todo"></app-todo-item> </ul>
      
      





disableAnimationコンポーネント変数の値を変更することにより、TodoItemコンポーネントのアニメーションを有効または無効にできます。 リポジトリのソースでdisableAnimation変数の値を制御するためのコードを確認できます。







重要なポイント:アニメーションが無効になっている場合、アニメーションの開始ハンドラーと停止ハンドラーが呼び出されます。 アニメーションが無効になったときに呼び出されたかどうかを理解するには、アニメーションイベントでdisabledパラメーターを使用する必要があります。 trueの場合、アニメーションは無効になっています。







アニメーションは正常に機能するようになりましたが、Angularでのアニメーションの可能性の限界にはほど遠いです。 他に何ができるか、アニメーションガイドでまだ説明されていないことを見てみましょう。







コンポーネントからアニメーションを削除し、パラメーターを使用する



製品内では、多くの場合、アニメーションは複数のコンポーネントで繰り返され、このアニメーションのパラメーター(アニメーションのタイミングまたは初期値と最終値)のみが異なります。 アニメーションを何度も複製しないようにするには、2つの解決策があります。







最初に頭に浮かぶのは、渡されたパラメーターに基づいて、トリガー関数を使用してアニメーショントリガーを作成した結果を返す関数を別のファイルに入れることです。 これは次のようなものです。







 export const todoAnimation = (timing, enterStart, enterStop, leaveStop) => { return trigger('todoAnimation', [ transition(':enter', [ style({ height: enterStart }), animate(timing, style({ height: enterStop })) ]), transition(':leave', [ animate(timing, style({ transform: 'scale(' + leaveStop + ')' })) ]) ]); }
      
      





このソリューションは非常に機能していますが、Angularの4番目のバージョンから、アニメーションを再利用できる2つの機能が追加されました。アニメーション機能のパラメーターの設定とuseAnimationによるアニメーションの使用です。 それらを順番に見てみましょう。







そのため、バージョン4以降、次の関数はアニメーションパラメーターの設定をサポートしています。







 state([...], { /* options */ }) transition([...], { /* options */ }) sequence([...], { /* options */ }) group([...], { /* options */ }) query([...], { /* options */ }) animation([...], { /* options */ }) useAnimation([...], { /* options */ }) animateChild([...], { /* options */ })
      
      





このリストからまだ多くの関数を検討していませんが、心配しないでください。それらについては後で説明します。 このリストの関数パラメーターは異なります。 そのため、たとえば、トランジション、シーケンス、グループ、およびアニメーション関数は、オプションとしてdelayとparamsの2つのパラメーターを取ることができます。







delayパラメーターを使用すると、アニメーションの開始を遅らせることができます。 値としてパーセンテージまたは負の値を使用することはできません。







paramsパラメーターを使用すると、特定のアニメーション関数を使用するときに値に代入されるパラメーターを関数に渡すことができます。 これがどのように起こるかは、以下の例で確認できます。







パラメータとして、タイミングパラメータまたはスタイル値パラメータのみを使用できます。 遅延は役に立たないので、状態関数はparamsパラメーターのみを受け入れます。これは論理的です。







残りの機能のパラメーターは、後で検討されます。 別の重要な点:関数でパラメーター置換を使用する場合、デフォルト値を指定する必要があります。指定しない場合、Angularはエラーをスローします。







パラメータを使用してアニメーション関数を書き直しましょう。







 export const todoAnimation = (timing, enterStart, enterStop, leaveStop) => { return trigger('todoAnimation', [ transition(':enter', [ style({ height: "{{ enterStart }}" }), animate("{{ timing }}", style({ height: "{{ enterStop }}" })) ], { params: { enterStart: 0, enterStop: 1, timings: '0.3s' } }), transition(':leave', [ animate(timing, style({ transform: 'scale({{ leaveStop }})' })) ], { params: { leaveStop: 0, timings: '0.3s' }}) ]); }
      
      





もう1つの重要なポイント:転送されたアニメーションパラメータは、アニメーションの開始時に置換され、アニメーション中に変更することはできません。

また、アニメーションの開始時にアニメーションパラメータを転送できることも重要です。 次のようになります。







 <div [@fadeAnimation]="{value: 'fadeIn', params: { start: 0, end: 1, timing: 1000 } }" >...</div>
      
      





バージョン4で追加された2番目の機能は、アニメーションとuseAnimationの2つの関数のセットです。 1つ目ではアニメーションを説明でき、2つ目ではコンポーネントメタデータで使用できます。 コンポーネントにfadeIn / fadeOutアニメーションがある場合、次のようになります。







 import { animation, style, animate } from "@angular/animations"; export const fadeAnimation = animation([ style({ opacity: "{{ from }}" }), animate("{{ time }}", style({ opacity: "{{ to }}" })) ], { time: "1s", to: 1, from: 0 })
      
      





次に、コンポーネントでアニメーションを使用します。







 import {useAnimation, transition} from "@angular/animations"; import {fadeAnimation} from "./animations"; ... transition('* => fadeIn', [ useAnimation(fadeAnimation, { from: 0, to: 1, time: '1s easy-in' }) ]), transition('* => fadeOut', [ useAnimation(fadeAnimation, { from: 1, to: 0, time: '1s easy-out' }) ])
      
      





ご覧のとおり、animateを使用してアニメーションを説明するときに、すべてのパラメーターの値を指定しました。 useAnimationでアニメーションを使用するときに一部のパラメーターが渡されない場合、アニメーションでデフォルトとして指定したパラメーターが使用されます。 新しいアニメーション機能を使用する前に、さらに2つの新しい機能を検討する必要があります。







クエリおよびステージャー関数



クエリ関数はelement.querySelectorAllに非常に似ています。 DOMから要素を選択し、それらの要素に対して一連のアニメーションを実行したり、これらの要素のスタイルを変更したりできます。 これにより、アニメーションをTodoItemコンポーネントからTodoListの上位レベルに転送し、アニメーションをより柔軟にすることができます。たとえば、各偶数要素のアニメーションを開始します。 クエリ関数は次のようになります。







 query('*', style({ opacity: 0 })) query('div, .inner, #id', [ animate(1000, style({ opacity: 1 })) ])
      
      





最初のパラメーターは、要素を選択するセレクターを示します。 現在、次のセレクタオプションがサポートされています。









上記のセレクターはすべて、たとえば次のように組み合わせることができます。







 query(':self, .record:enter, .record:leave, @subTrigger', [...])
      
      





ご覧のとおり、これにより、要素のより複雑なアニメーションを作成する多くの機会が提供されます。







クエリ関数は、使用時にパラメーターを受け取ることもできます。また、上記で説明したdelayおよびparamsパラメーターに加えて、さらに2つのオプションと制限をサポートします。







デフォルトでは、セレクターの下に要素が見つからなかった場合、例外がスローされます。 要素の不在がエラーではない場合、オプションのパラメーターとしてtrueを指定できます。 この場合、要素の不在は通常の状況と見なされます。







2番目のパラメーターlimitは、クエリを使用してすべての要素を選択するのではなく、指定した数だけを選択できるようにします。 limitパラメーターに負の値を指定すると、要素はリストの最後から選択されます。







2番目の関数であるstaggerは、クエリアニメーションと組み合わせて使用​​すると便利です。 ngForを介してユーザーに通知を表示するコードがあるとします:







 <div [@notificationAnimation]="notifications.length"> <div *ngFor="let notification of notifications"> {{ notification }} </div> </div>
      
      





そして、クエリを使用して追加された要素にフェードアニメーションがあります。







 trigger('notificationAnimation', [ transition('* => *', [ query(':enter', [ style({ opacity: 0 }), animate('1s', style({ opacity: 1 })) ]) ]) ])
      
      





アプリケーションを起動すると、通知のリストが一度に表示されることがわかりますが、これは私たちが望むものではありません。 通知を順番に表示し、少し遅れて表示したり、表示を消したりしたいと思います。 この効果を実装するには、stagger関数を使用できます。







 trigger('notificationAnimation', [ transition('* => *', [ query(':enter', stagger('100ms', [ animate('1s', style({ opacity: 1 })) ]) ]) ])
      
      





スタガー機能は、要素のアニメーションを開始する前に遅延を追加します。これにより、外観がより滑らかで正確になります。 通知の消失をスムーズにするには、スタガー機能の動作を逆にする必要があります。 これを行うには、関数呼び出しのパラメーターに負の値を指定するか、単に逆パラメーターを追加します。







 stagger('100ms reverse', [...])
      
      





それでは、クエリ関数を使用してタスクのアニメーションを書き換えましょう。 これを行うには、TodoItemコンポーネントからTodoListコンポーネントに転送します。







 @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', styleUrls: ['./todo-list.component.css'], animations: [ trigger('todoList', [ transition('* => *', [ query(':enter', [ style({ height: 0 }), animate('0.3s ease-in', style({ height: '*' })) ], { optional: true }), query(':leave', [ animate('0.3s ease-out', style({ transform: 'scale(0)' })) ], { optional: true }) ]) ]) ] }) export class TodoListComponent implements OnInit, AfterViewInit { ... }
      
      





そして、to-doリストのテンプレートに接続します:







 <ul class="todo-list" [@.disabled]="disableAnimation" [@todoList]="todos.length"> <app-todo-item *ngFor="let todo of todos; trackBy: trackById" [todo]="todo" (itemRemoved)="remove($event)" (itemModified)="update($event)" ></app-todo-item> </ul>
      
      





アニメーションで遊んだ後、要素の外観が正常に機能することがわかりますが、リストからタスクを削除するとアニメーションなしで処理されます。 これは、to-doリストの再構築がアニメーションを実行する時間よりも早く発生するためです。 問題を修正する前に、アニメーションの別の革新を検討する必要があります。







子供をアニメートする



アプリケーションが要素の複数のグループを同時にアニメーション化する必要があり、グループの1つが2番目のDOMの親である場合、親要素のアニメーションは子のアニメーションよりも優先され、子のアニメーションはブロックされます。 次のコードがコンポーネントテンプレートにあるとします。







 <div [@parentAnimation]="exp"> <header>Hello</header> <div [@childAnimation]="exp"> one </div> <div [@childAnimation]="exp"> two </div> <div [@childAnimation]="exp"> three </div> </div>
      
      





ご覧のとおり、1つのコンポーネント変数が変更されると、親と子のアニメーションが開始されます。 同時に。 , .







, animateChild, . :







 @Component({ selector: 'parent-child-component', animations: [ trigger('parentAnimation', [ transition('false => true', [ query('header', [ style({ opacity: 0 }), animate(500, style({ opacity: 1 })) ]), query('@childAnimation', [ animateChild() ]) ]) ]), trigger('childAnimation', [ transition('false => true', [ style({ opacity: 0 }), animate(500, style({ opacity: 1 })) ]) ]) ] })
      
      





animateChild : , , — duration. , , 0 .







animateChild() , . , :







 <ul class="todo-list"> <li *ngFor="let todo of todos; trackBy: trackById" @todoList> <app-todo-item @todoItem [todo]="todo" (itemRemoved)="remove($event)" (itemModified)="update($event)" ></app-todo-item> </li> </ul>
      
      





:







 @Component({ selector: 'app-todo-list', templateUrl: './todo-list.component.html', styleUrls: ['./todo-list.component.css'], animations: [ trigger('todoList', [ transition(':enter, :leave', [ query('@*', animateChild()) ]) ]), trigger('todoItem', [ transition(':enter', [ useAnimation(enterAnimation) ]), transition(':leave', [ useAnimation(leaveAnimation) ]) ]) ] })
      
      





, , DOM. child-animation.







. animateChild , . , .









, .







, , , , , . , .







, , , . , , . , , .







, , DI. . :







 import { AnimationBuilder } from "@angular/animations"; @Component({...}) class MyCmp { constructor(public builder: AnimationBuilder) {} animate() { const factory = this.builder.build([ //     ]); const player = factory.create(this.someElement); player.play(); } }
      
      





, :







– play, pause, restart, finish, reset. , , . , onDone(fn: () => void): void onStart(fn: () => void): void.







, -, .







, , , . , , animation-player .











. - :







 set percentage(p: number) { const lastPercentage = this._percentage; this._percentage = p; if (this.player) { this.player.destroy(); } const factory = this._builder.build([ style({ width: lastPercentage + '%' }), animate('777ms cubic-bezier(.35, 0, .25, 1)', style({ width: p + '%' })) ]); this.player = factory.create(this.loadingBar.nativeElement, {}); this.player.play(); }
      
      





, . , , . . , , . , . , . , .









, DOM . しかし、これでは十分ではありません。 , .







, - router-outlet , , , router-outlet, . router-outlet . ? , :







 <div [@routeAnimation]="prepRouteState(routerOutlet)"> <router-outlet #routerOutlet="outlet"></div> <div>
      
      





, prepRouteState, :







 @Component({ animations: [ trigger('routeAnimation', [ transition('homePage => supportPage', [ // ... ]), transition('supportPage => homePage', [ // ... ]) ]) ] }) class AppComponent { prepRouteState(outlet: any) { return outlet.activatedRouteData['animation'] || 'firstPage'; } }
      
      





, , data, :







 const ROUTES = [ { path: '', component: HomePageComponent, data: { animation: 'homePage' } }, { path: 'support', component: SupportPageComponent, data: { animation: 'supportPage' } } ]
      
      





, :







 @Component({ ... animations: [ trigger('routeAnimation', [ transition('completed <=> all, active <=> all, active <=> completed', [ query(':self', style({ height: '*', width: '*' })), query(':enter, :leave', style({ position: 'relative' })), query(':leave', style({ transform: 'scale(1)' })), query(':enter', style({ transform: 'scale(0)' })), group([ query(':leave', group([ animate('0.4s cubic-bezier(.35,0,.25,1)', style({ transform: 'scale(0)' })), animateChild() ])), query(':enter', group([ animate('0.4s cubic-bezier(.35, 0, .25, 1)', style({ transform: 'scale(1)' })), animateChild() ])) ]), query(':self', style({ height: '*', width: '*' })), ]) ]) ] }) export class AppComponent { prepRouteState(outlet: any) { if (outlet.isActivated) { return outlet.activatedRoute.data.getValue()['status'] || 'all'; } } }
      
      





, . all active, . , , . :self, DOM. , query(':self', style({ height: '*', width: '*' })), , DOM . .







, DOM, — . enter/leave. , , group. , .







, — animateChild , , . .







おわりに



, . , , , , , , . , , . -.








All Articles