AngularJSとCSS Transitionsの話


# はじめに

AngularJSを少しでも触ったことのある方なら、データバインディングによって簡単に動的なWebページが作れるのはご存じでしょう。
本稿では、動的に変化させるWebページに[CSS Transitions](http://www.w3.org/TR/css3-transitions/)を使ったアニメーションを加えることで、Webページの見た目にも動きを加えるテクニックについて紹介したいと思います。

Transitionとは遷移といった意味です。
[CSS Animations](http://www.w3.org/TR/css3-animations/)という規格も存在するんですが、CSS Animationsは繰り返しのあるアニメーションの表現に強いです。
CSS TransitionsはCSSプロパティの値の変更、つまり遷移を、一瞬の切り替わりではなく連続的な変化で表現します。

AngularJSのバージョンは1.4.8で確認しています。

# ngAnimate

AngularJSでアニメーションといえばまず[ngAnimate](https://docs.angularjs.org/api/ngAnimate)が思い浮かぶと思います。
はじめにngAnimateとCSS Transitionsの関係から紹介しましょう。

さて、ngAnimateのドキュメントを見ると「CSS-based Animations」の説明から入っています。
簡単なサンプルを示しましょう。
まずはアニメーションなしで、ngShowを使ってチェックボックスの値に応じて要素の表示非表示を切り替えるコードです。

```html
<div ng-app="app">
  <div ng-controller="MainController">
    <button ng-click="toggleShow()">toggle</button>
    <div class="message" ng-show="show">
      hello
    </div>
  </div>
</div>
```

```javascript
var MainController = function ($scope) {
  $scope.show = false;
  
  $scope.toggleShow = function () {
    $scope.show = !$scope.show;
  };
};

angular.module('app', [])
  .controller('MainController', MainController);
```

https://jsfiddle.net/xj7fz3fz/ で動作確認できるようにしています。
今はチェックボックスの値を変えると、即座に表示のオンオフが切り替わっています。
これを、徐々に透明になるように、あるいは透明な状態から徐々に現れてくるように変更します。

はじめに、ngAnimateの依存モジュールへの追加を行います。
angular-animateをscriptタグなどで読み込むのを忘れないでください。

```javascript
angular.module('app', ['ngAnimate'])
  .controller('MainController', MainController);
```

次にスタイルを追加します。

```css
.message {
  opacity: 1;
}

.message.ng-hide-add, .message.ng-hide-remove {
  transition: all linear 0.5s;
}

.message.ng-hide {
  opacity: 0;
}
```

これだけでアニメーションの追加が行えます。
動作は https://jsfiddle.net/t8j818xo/ で確認できます。

少しだけ解説をしましょう。
スタイルの方はAngularJS独自の要素は何もない標準のCSSです。
`transition`プロパティがCSS Transitionsのキモとなります。
`transition`プロパティは、`transition-property`、`transition-duration`、`transition-timing-function`、`transition-delay`の4つのプロパティの省略表記です。
上の例では`all linear 0.5s`と3つの値を与えていて、順に`transition-property`、`transition-timing-function`、`transition-delay`に対応しています。

`transition-peroperty`では、アニメーション対象にするCSSプロパティを指定します。
上の例では`all`を与えているので全てのCSSプロパティを対象とします。
上の例では`opacity`のみを変化させているので、`all`の代わりに`opacity`と書くこともできます。

`transition-duration`はアニメーションの時間を指定します。
上の例では`0.5s`を与えていて、`opacity`の値が0から1に、あるいは1から0に0.5秒かけて連続的に変化します。

`transition-timing-function`では、CSSプロパティの変化の仕方を指定します。
上の例では`linear`を与えているので、アニメーション開始時点から終了時点までプロパティを線形に変形します。
上の例では`transition-delay`が`0.5s`なので、アニメーション開始から0.25秒後の`opacity`の値は大体0.5になります。
他にも組み込みのtiming functionが存在しているので詳しくは http://www.w3.org/TR/css3-transitions/#transition-timing-function-property などを参照してください。

ngAnimateの話に戻ります。
ngAnimateを読み込んでいると、ngShowやngIf、ngRepeat、ngViewといったいくつかのディレクティブで要素が変化するときに`class`の追加や削除が行われるようになります。
ngShowでは、要素が非表示のときに`ng-hide`が`class`に追加されます。
ngAnimateを読み込んでいると、ngShowの値がtrueからfalseに切り替わるときに`ng-hide-add`、falseからtrueに切り替わるときに`ng-hide-remove`が一瞬だけ`class`に追加されます。
そのタイミングで`transition`プロパティを適用することでアニメーションを実現しています。

# CSS Transitions

ngAnimateではCSS Transitionsを使うことで少ない記述でWebページにアニメーションを追加できることが確認できました。
CSS Transitionsは非常に強力で、ngAnimateを使わなくても便利になる場面があります。

例えば、ある要素の座標をCSSの`transform`プロパティで設定してるとして、Scope中の変数の値で`transform`プロパティの値が変化するとしましょう。
HTMLとJavaScript、CSSは以下のようなコードになります。

```html
<div ng-app="app">
  <div ng-controller="MainController">
    <div>
      <label>x</label><input type="range" min="0" max="500" step="50" ng-model="x">
    </div>
    <div>
      <label>y</label><input type="range" min="0" max="500" step="50" ng-model="y">
    </div>
    <div class="message" ng-style="messageStyle()">
      hello
    </div>
  </div>
</div>
```

```javascript
var MainController = function ($scope) {
  $scope.x = 50;
  $scope.y = 50;
  
  $scope.messageStyle = function () {
    return {
      transform: 'translate(' + $scope.x + 'px,' + $scope.y + 'px)'
    };
  };
};

angular.module('app', [])
  .controller('MainController', MainController);
```

```css
.message {
  width: 50px;
  height: 50px;
  background-color: red;
}
```

xとyをバインディングしているだけの単純なコードです。
input要素でxとyを変更すると即座にmessageの位置が変化すると思います。

ここで、CSSにCSS Transitionsの記述を以下のように追加します。

```css
.message {
  width: 50px;
  height: 50px;
  background-color: red;
  transition-property: transform;
  transition-duration: 1s;
}
```

動作確認は https://jsfiddle.net/hyuavLkn/1/ でできます。
messageの位置がアニメーションで連続的に変化するようになりました。
このようにngAnimateを使わない場合でも、AngularJSのデータバインディングとCSS Transitionsを組み合わせることで面白い動きのWebページが簡単につくれます。

# おわりに

私は普段から可視化アプリケーションの開発を行っているんですが、ユーザーの操作による可視化結果の変化をアニメーションで表現することは当たり前になっています。
可視化のライブラリといえばD3.jsが有名だと思いますが、私の場合はアプリケーションの大規模化につれて開発が苦しくなってきたので、AngularJSなどを使って可視化部分の開発も行うようになってきました。
D3.jsが持っているアニメーションの機能は非常に強力なのですが、AngularJSでは可視化のためのアニメーションという点ではサポートがほとんどありません。
しかし、CSS Transitionsを使うことで、AngularJSでもD3.jsにも負けない強力なアニメーション表現ができるようになるので覚えてみて損はないかと思います。