ngAnimateを使ってjQueryのslideToggleちっくなものを作ってみた

どうもこんにちは。箸休め担当です。
こちらは [AngularJS Advent Calendar 2014](http://qiita.com/advent-calendar/2014/angularjs) の 22日目の投稿です。

昨年の Advent で [Angular.jsで実践 アニメーション](http://qiita.com/icoxfog417/items/2425b2b3dd70cef3f705) という素晴らしいまとめが発表されているのですが、
実際 jQuery でできていたものが AngularJS でどれだけ再現できるだろう？
と思って作ってみました。

今回選んだのは、jQueryの [slideToggle](http://semooh.jp/jquery/api/effects/slideToggle/%5Bspeed%5D,+%5Bcallback%5D/) 
クリックするとびろ～んと出てくるアレです。

## まずはアニメーションを付けずに Toggle の動きをさせる

`class="acc-parent"` のエリアをクリックすると `class="acc-child"` が表示／非表示に切り替わるという動きです。

```html:sample.html
<div ng-app="myApp">
    <div class="accordion-contain" ng-controller="accordionCtrl">
        <div class="acc-parent" ng-click="accOpen()">Test title</div>
        <div class="acc-child" ng-show="bodyShow">Test body</div>
    </div>
</div>
```

```javascript:sample.js
var myApp = angular.module('myApp',[]);

myApp.controller('accordionCtrl',['$scope', function($scope){
    // ロード時 "acc-child" は非表示
    $scope.bodyShow = false;
    $scope.accOpen = function(){
        // bodyShowをtrue／falseにすることで表示の切り替えを行う
        $scope.bodyShow = !$scope.bodyShow;
    };
}]);
```

この時点で CSS は背景や文字の色付けにしか使わないので省略。

デモは [こちら](http://jsfiddle.net/k27v74k4/)
パカパカ開くようになりました。

## ngAnimate を注入して動きをつける

こちらを参考に動きをつけてみます。
[AngularJSのアニメーションの仕組みについて](http://js.studio-kingdom.com/angularjs/tips/how_to_animation)

まずはアニメーション用の js ファイルを読み込ませます。

```html:sample.html
<!-- 以下のタグを追加 -->
<script src="http://code.angularjs.org/1.2.3/angular-animate.min.js"></script>
```

次に、モジュールの宣言箇所を以下のように修正します。

```javascript:sample.js
// ngAnimate を注入させる
var myApp = angular.module('myApp',['ngAnimate']);
```

ここまでが下準備です。

要素の表示／非表示に `ngShow` を使用している場合、
非表示時に **ng-hide** がクラスに追加されます。（表示時は削除される）
その動きを利用して、CSS に次のような記述を入れます。
(Chromeの場合)

```css:sample.css
/* 表示・非表示切替時のアニメーション */
.acc-child.ng-hide-add,
.acc-child.ng-hide-remove{

    /* 全ての要素を、一定の速度で、1秒間表現する */
    -webkit-transition:all linear 1s;

    /* アニメーション実行前に非表示にされるのを回避 */
    display:block!important;
}

/* acc-child 非表示 */
/* ng-hide が追加されたときに適用される */
.acc-child.ng-hide-add.ng-hide-add-active,
.acc-child.ng-hide-remove {
    opacity:0;
}

/* acc-child 表示 */
/* ng-hide が削除されたときに適用される */
.acc-child.ng-hide-remove.ng-hide-remove-active,
.acc-child.ng-hide-add {
    opacity:1;
}
```

`opacity` で要素の透明度を指定します。
ng-hideが追加されたときには `opacity` が 1 (不透明) → 0 (透明) へ、
削除されたときは 0 → 1 へとアニメーションが実行されるという指定です。

デモは [こちら](http://jsfiddle.net/x750xamt/)
ふわ～っと表示されるようになりました。
ただ、僕がやりたいのはこれじゃない。ふわ～じゃなくて、びろ～んです。

## より slideToggle らしい動作へ

ここからは CSS の話です。

先程 `opacity` プロパティを使用し透明度に変化をつける話をしました。
つまり、この部分を `opacity` 以外のもので変化させれば
slideToggle 風の動きが実現できそうですね。

今回は `transform: translate(x, y)` で acc-child を移動させる方法をとります。

```css:sample.css
/* acc-child非表示 */
/* ng-hideが追加されたときに適用される */
.acc-child.ng-hide-add.ng-hide-add-active,
.acc-child.ng-hide-remove {
    -webkit-transform:translate(0px,-36px);
}

/* acc-child表示 */
/* ng-hideが削除されたときに適用される */
.acc-child.ng-hide-remove.ng-hide-remove-active,
.acc-child.ng-hide-add {
    -webkit-transform:translate(0px,0px);
}
```

`class="acc-parent"` 、 `class="acc-child"` ともに縦の幅が 36px のため、
Y軸方向に 36px 分移動するよう指定しました。
非表示時、acc-child は acc-parent に重なるため、マイナス幅の移動になります。

ただ、このままで動作すると acc-child が acc-parent よりも前面に来てしまいます。
背面で移動させるために、各要素に以下のプロパティを追加します。

```css:sample.css
.acc-parent {
    background: hsl(90, 60%, 70%);
    color: hsl(90, 30%, 30%);
    padding: 10px;
    /* acc-parentが必ず前面に来るように設定 */
    position: relative;
    z-index:1;
}

.acc-child {
    background: hsl(100, 80%, 90%);
    color: hsl(90, 30%, 30%);
    padding: 10px;
    /* acc-parentが必ず前面に来るように設定 */
    position: relative;
    z-index:0;
}
```

`z-index` で要素の重なりの順番を指定します。（この場合、値の大きいacc-parent の方が前面）
また、`z-index` を指定する場合は **position: static; 以外** の指定が必要になるため、
`position: relative;` も指定してあげます。

デモは [こちら](http://jsfiddle.net/x750xamt/1/)

slideToggle らしくなりましたね！！

## その他参考にしたページ

[How to create an accordion with AngularJS](http://zachwills.net/how-to-create-an-accordion-with-angular-js/) 
（最初はアコーディオンを作る予定だったので。。。）
[Stack Overflow - Slide up/down effect with ng-show and ng-animate](http://stackoverflow.com/questions/16677304/slide-up-down-effect-with-ng-show-and-ng-animate)
[CSS3 アニメーション（Transitions）の使用方法](http://www.css-lecture.com/log/css3/css3-transition.html)
[CSS3 変形処理を行う transform プロパティ](http://www.css-lecture.com/log/css3/css3-transform.html)
[要素の重なりについて本気出して考えてみた（z-indexとか何とかとか）](http://no1026.com/archives/104)