注:古い記事の為、内容が最新ではない可能性がありますm(_ _)m
Applying Animation
こんにちわ。新米エンジニアのマツウラです。
最近英文をよく読んでいるためか英語の苦手意識が薄れた気がします。
今回はAngular1.2から新たに追加されたAnimationのチュートリアルです。
とはいえ、ほぼ公式の翻訳みたいなもんです。
英語はまだまだなので間違ってたらツッコミください^^;
directiveにcssとjavascriptからなるアニメーションを追加することができます。
angular-animate.jsにバンドルされ、アプリモジュールと依存関係とするとアニメーションが可能になります。
ng
directiveは自動でアニメーションを割りこませて起動します。(ngRepeatへの挿入・削除、ngClassへの追加・削除など。)
Template
HTMLテンプレートで変更が必要なのは、アニメーションが定義されているassetファイルと、angular-animate.js
をリンクさせることです。
アニメーションモジュールであるngAnimate
はangular-animate.js
で定義されています。
Important: jQueryは1.10.x
を使ってください。AngularJSは2.x
をサポートしてません。
アニメーションは現在(2014/03/13)CSS(animations.css)だけでなくJavascript(animations.js)内にも作れます。アニメーションを構築する前にngAnimate
モジュールを依存関係として新しいモジュールを作成します。
Module & Animations
angular.module('phonacatAnimations', ['ngAnimate']). // 後にここでアニメーションを定義します。 ... angular.module('phonecat', [ 'ngRoute', 'phonecatAnimations', 'phonecatControllers', 'phonecatFilters', 'phonecatServices' ]).
これでphonecatモジュールがアニメーションを認識しました。
次にアニメーションを作ります。
ngRepeatをアニメーション化
phone-list.html
のngRepeat
directiveにCSS Transitionアニメーションを追加します。
CSSアニメーションコードでdirectiveにフック出来るように、リピート要素にCSSクラスを追加します。
<ul class="phones"> <li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail phone-listing"> <a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a> <a href="#/phones/{{phone.id}}">{{phone.name}}</a> <p>{{phone.snippet}}</p> </li> </ul>
phone-listing
クラスを追加しました。これはアニメーション動作に必要なHTMLコードです。
実際のCSS Transitionコードは次のとおり。
.phone-listing.ng-enter, .phone-listing.ng-leave, .phone-listing.ng-move { -webkit-transition: 0.5s linear all; -moz-transition: 0.5s linear all; -o-transition: 0.5s linear all; transition: 0.5s linear all; } .phone-listing.ng-enter, .phone-listing.ng-move { opacity: 0; height: 0; overflow: hidden; } .phone-listing.ng-move.ng-move-active, .phone-listing.ng-enter.ng-enter-active { opacity: 1; height: 120px; } .phone-listing.ng-leave { opacity: 1; overflow: hidden; } .phone-listing.ng-leave.ng-leave-active { opacity: 0; height: 0; padding-top: 0; padding-bottom: 0; }
phone-listingクラスはリストに挿入、削除したときに起こるアニメーションのフックと一体化してます。
ng-enter
新しい携帯がリストに追加され、ページ上に表示された時に適用ng-move
項目がリスト内を移動している時に適用ng-leave
リストから削除された時に適用
phone-listingはng-repeat
属性に渡されたデータに応じて追加・削除されます。
例えば、フィルタを変更するとリストがアニメーションします。
重要な点は、アニメーション実行の際、要素に2組のCSSクラスが追加されることです。
- アニメーション開始を表す"starting"クラス
- アニメーション終了を表す"active"クラス
"starting"クラスの名前は発生したイベント名(enter
,move
,leave
のような)にng-
の接頭辞が付いたものです。つまりenter
イベントならng-enter
ということです。
"active"クラス名も"starting"クラスと同じですが、-active
の接尾辞が付いています。
この2つのクラスの命名規則に従うことで開発者はアニメーションを作ることができます。
上記の例では、要素のheightが0~120pxに展開します。
同時にフェードイン、フェードアウトが発生します。
これら全ては上記サンプルCSSの最上部、CSS transition宣言によって処理されます。
多くのモダンブラウザはCSS TransitionsとCSS Animationsをサポートしていますが、IE9とそれ以前は非サポートです。
古いブラウザとの下位互換性を維持したい場合は、以下に記載されているJavascriptベースのアニメーションの使用を検討してください。
CSS Keyframe AnimationsでngViewをアニメーション化
次はngView
でルート変更をする間にtransitionsアニメーションを追加します。
はじめにng-view directiveを含む要素にCSSクラスを追加します。
Viewを変更する際のアニメーションをより細かく制御するため、HTMLに若干の変更を加えます。
<div class="view-container"> <div ng-view class="view-frame"></div> </div>
ng-viewはview-containerクラスを基準としてアニメーションするように変更します。
view-containerにはposition: relative
クラスを追加します。(以下のcss参照)
ということで、animations.cssにtransitionアニメーションのための記述を追加します。
.view-container { position: relative; } .view-frame.ng-enter, .view-frame.ng-leave { background: white; position: absolute; top: 0; left: 0; right: 0; } .view-frame.ng-enter { -webkit-animation: 0.5s fade-in; -moz-animation: 0.5s fade-in; -o-animation: 0.5s fade-in; animation: 0.5s fade-in; z-index: 100; } .view-frame.ng-leave { -webkit-animation: 0.5s fade-out; -moz-animation: 0.5s fade-out; -o-animation: 0.5s fade-out; animation: 0.5s fade-out; z-index:99; } @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @-moz-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @-webkit-keyframes fade-in { from { opacity: 0; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-moz-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } @-webkit-keyframes fade-out { from { opacity: 1; } to { opacity: 0; } } /* don't forget about the vendor-prefixes! */
このCSSで特別なことは何もしてません。
ページ間のフェードインとフェードアウト効果だけです。
ここで少々特殊な記述なのは、クロスフェードアニメーションを実行している間に、前ページの上に次ページを配置するためposition:absoluteを使っているabsoluteを使っている部分だけです。
(前ページはng-leave
を持つ要素。次ページはng-enter
を介して特定する。)
前ページが削除される時、新しいページが上に重なるようにしてフェードインします。
一度leaveアニメーションが終了すると要素は削除されます。
一度enterアニメーションが完了すると、その後、元のCSSに書かれたポジションと同じならng-enter
とng-enter-active
クラスは要素から削除されます。
ルートを変更している間にも流れるようにアニメーションは動作します。
ng-repeat
同様にCSSクラスが適用されます。(startとendクラス)
新しいページが読み込まれるたび、ng-view directiveは自身のコピーを作成してテンプレートをダウンロードしてコンテンツを追加します。
これは全てのviewが単一のHTMLに含まれることを保証し、アニメーションの制御を簡単にします。
CSSアニメーションの詳細についてはWeb Platform documentationを参照してください。
JavascriptでngClassのアニメーション化
アプリに別のアニメーションを追加します。
phone-list.htmlページに切り替えると、サムネイルがあります。
サムネイルをクリックすると携帯画像が変わります。
このあたりにアニメーションを追加するにはどうすれば良いでしょうか?
基本的に、サムネイルをクリックすると新しく選択されたサムネイルを反映させるためプロファイル画像の状態を変更します。
HTML内の状態を変更する最良方法はCSSクラスを使うことです。
先ほどのCSSを使ったアニメーション指定ではなく、今回はCSSクラス自体が変更されるたびアニメーションが発生します。
新しい携帯サムネイルが選択されるたび状態が変化し、.active
クラスが一致するプロファイル画像とアニメーションの再生に追加されます。
はじめにphone-detail.html
を微調整します。
<!-- We're only changing the top of the file --> <div class="phone-images"> <img ng-src="{{img}}" class="phone" ng-repeat="img in phone.images" ng-class="{active:mainImageUrl==img}"> </div> <h1>{{phone.name}}</h1> <p>{{phone.description}}</p> <ul class="phone-thumbs"> <li ng-repeat="img in phone.images"> <img ng-src="{{img}}" ng-mouseenter="setImage(img)"> </li> </ul>
サムネイルも同様に、リストとして全てのプロファイル画像を表示するためにng-repeatを使っています。
しかしng-repeat関連のアニメーション化はしていません。
代わりにng-class directiveでactive
クラスがtrueであれば、要素に適用され表示されます。
その他のプロファイル画像は非表示です。
今回はactiveクラスを持つ要素が常に1つ存在します。そのため常に画面上には1つプロファイル画像が表示されています。
activeクラスが要素に追加されたとき、active-add
とactive-add-active
クラスがアニメーションの終了をAngularJSに通知する直前に追加されます。
削除されると、active-remove
とactive-remove-active
クラスが次々と他のアニメーショントリガの要素に適用されます。
CSS対応のアニメーションを作るようなものだと考えてください。
Javascript対応のアニメーションをanimation()
メソッドモジュールで作成します。
var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']); phonecatAnimations.animation('.phone', function() { var animateUp = function(element, className, done) { if(className != 'active') { return; } element.css({ position: 'absolute', top: 500, left: 0, display: 'block' }); jQuery(element).animate({ top: 0 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } var animateDown = function(element, className, done) { if(className != 'active') { return; } element.css({ position: 'absolute', left: 0, top: 0 }); jQuery(element).animate({ top: -500 }, done); return function(cancel) { if(cancel) { element.stop(); } }; } return { addClass: animateUp, removeClass: animateDown }; });
アニメーション実装にjQueryを使っているので注意。
jQueryは必須ではありませんが、独自のアニメーションライブラリを作成することはチュートリアルの範疇外なので使用しています。
jQueryアニメーションの詳細はjQuery documentaionを参照してください。
addClass
とremoveClass
のコールバック関数は、登録したクラスが含まれる要素でクラスが追加・削除された際に呼ばれます。今回なら.phone
クラスです。
要素に.active
クラスが追加されると(ng-class
directiveを介して)、addClass
コールバックはパラメータとして渡されたelement
で呼び出されます。
最後のパラメータに渡されたのはdone
コールバック関数です。
Javascriptアニメーションが終了したことを、done
が呼ばれることでAngularJSは知ることができます。
removeClass
コールバック関数も同様に動作しますが、クラスが要素から削除された時にトリガされます。
Javascriptコールバックでは、DOM操作によりアニメーションを作成します。
上記のコードでは、element.css()
とelement.animate()
が行っています。
コールバックは各項目を500px上昇させることで、前の項目と新しい項目の両方をアニメーションさせて次の要素を配置しています。
この結果、ベルトコンベアーのようなアニメーションになります。
animate
関数が役目を終えた後、done
が呼ばれます。
addClass
とremoveClass
の各関数の戻り値に注意してください。
これはオプション関数で、アニメーションが完了した時だけでなくキャンセルされた時(他のアニメーションが同じ要素で起きた時)にも呼ばれます。
bool型のパラメータが関数に渡されるので、アニメーションがキャンセルされたか否か開発者が知ることができます。
この関数はアニメーションが終了した際に必要なクリーンアップを行うことができます。
以上、追加されたAnimationチュートリアルでした。