jQuery その先のUIアニメーション

# jQueryを使わないでアニメーションしたくはないでしょうか

別に興味ないですか。私は興味あります。

[ブレイクスルーJavaScript フロントエンドエンジニアとして越えるべき5つの壁―オブジェクト指向からシングルページアプリケーションまで](http://www.amazon.co.jp/gp/product/479813905X)を注文しました。

jQueryその先へ、とかそういう文言が今熱いのか。
積ん読たまってて読めるかまったくわからないんですけど。

書籍とは離れますが、jQuery を使用しないでどこまでアニメーションに耐えられるのか、ちょっとやってみました。

https://github.com/tkdn/postjQueryAnimate
※ jQuery は2~使ったので、IE10～,Chrome,Safari,Firefoxいずれも最新くらいがターゲット

## Velocity.js という選択

最初はCSSアニメーションでなんとかと思ってたんですが、途中で挫折、jQueryの`.animate`を使わないでFPSを設定、数値の変更なんか自前でやってられないのと、[パフォーマンスが優れている](http://beijaflor.hatenablog.jp/entry/2014/06/18/090706)ということで、[Velocity.js](http://julian.com/research/velocity/)を選びました。

[jQueryのプラグインであるかのような誤解が生まれていますが](http://qiita.com/kyota/items/754e0e6cb7a144eda850)、スタンドアローンとしても使えます。今回は公式にもあるような使い方で、browserify で。

## 2つのソース

### jQueryだとこんな感じ

```js
$(function() {
  var action = (function(jqObject) {
    var content = jqObject.next('.content');
    var contentParent = content.parent();
    if(contentParent.hasClass('active')){
      content.slideUp('250',function(){
        contentParent.removeClass('active');
      });
    } else {
      contentParent.addClass('active');
      content.slideDown('250');
    }
  });
  $('.jq-accordion').on('click', function(event) {
    event.preventDefault();
    action($(this));
  });
});
```

`action`でアコーディオンに関する動きをひとまとめにしてます。動きをバインドさせたい要素（jQueryオブジェクト）を引数として、

* 隣接する次要素`.content`をアコーディオンの内容とし、これをアニメーション（slideUp,slideDown）
* その親要素（リポジトリでは`li`）に活性非活性のクラス`active`をtoggleさせ、UIとしての状態を明示、アクションのshow/hideを決めてる
* 最後に`.jq-accordion`の要素にactionをバインドさせてる

みたいな感じです。ありがちですね。

## これをVelocity.jsとブラウザネイティブでやる

Velocity.js をモジュールとしてロード、グローバル空間にexportしてやる。npm で検索して出てくる、[velocity.js](https://www.npmjs.com/package/velocity.js)は全く別物なので注意。`velocity-animate`が本物。

リポジトリではnpm installしたら必要なの全部落ちてきます。

```
var Velocity = require('velocity-animate');
```

魔法のrequireでOK。あとはbrowserifyしてあげるだけ。

### ソース

```js
document.addEventListener('DOMContentLoaded', function(event) {
  var accodionContents = this.querySelectorAll('.js-accordion + .content');
  for (var i = 0; i < accodionContents.length; i++) {
    var height = accodionContents[i].offsetHeight;
    accodionContents[i].setAttribute('data-height', height);
    accodionContents[i].style.height = 0;
  }
  var action = (function(elemObject){
    var content = elemObject.nextElementSibling;
    var contentParent = elemObject.parentElement;
    var contentHeight = content.getAttribute('data-height');
    if(contentParent.classList.contains('active')){
      contentParent.classList.remove('active');
      Velocity(content, { height: 0 }, 250);
    } else {
      contentParent.classList.add('active');
      Velocity(content, { height: contentHeight }, 250);
    }
  });
  var actionElem = this.querySelectorAll('.js-accordion');
  for (var i = 0; i < actionElem.length; i++) {
    actionElem[i].addEventListener('click',function(event){
      event.preventDefault();
      action(this);
    });
  }
});
```

jQuery使ってるとおまじないのように、`$(function(){...});`でDOMREADYで実行させてるのもちゃんとやる。documentの`DOMContentLoaded`イベントで実行です。

### あらかじめ隠しておいた要素に height: auto; を適用するのは無理

jQueryの時とちょっと違うのは、高さを考えないとうまくいかないってことでした。

```js
Velocity(elem, { height: 'auto' }, 250);
```
みたいなことは出来ない。jQueryの時は要素を`display:none;`していてもアニメーションできていたけど、Velocity.jsではできない。

ちなみに、CSS3 transition はlength, percentageしか受け付けないのでもちろん`auto`は値として無理、`max-height`や`margint-top`のネガティブマージンでなんとかしてるのを見たんだけど、もうcssでごちゃごちゃやりたくないんですよ。

というわけで、jsでなんとかした。

```js
  var accodionContents = this.querySelectorAll('.js-accordion + .content');
  for (var i = 0; i < accodionContents.length; i++) {
    var height = accodionContents[i].offsetHeight;
    accodionContents[i].setAttribute('data-height', height);
    accodionContents[i].style.height = 0;
  }
```

そもそも`display:none;`しない、DOMloaded時には高さがある状態を作って、そいつらの高さを`data-height`属性としてDOMに埋め込み、`style`属性にheight:0;をセット、をforで回す。全然スマートじゃない気がするけど。

あとは、IE8.9 あたりじゃ使えないのを使います。[classList](https://developer.mozilla.org/ja/docs/Web/API/Element/classList)とかそうですね。

### ハマりかけたaddEventListenerのところ

```js
document.querySelectorAll('.js-accordion').addEventListener('click',function(){
...
});
```

みたいなことしたらできないですよね。`document.querySelectorAll('.js-accordion')`が返してくるのdocumentにある要素全ての配列だから。なので、回して各要素にバインドさせる。

### ちょっと気になってるところ

jQueryでもVelocityでもdurationは250msのはずなんだけど、Velocityの方が早い気が…。

---

やってみて思ったんですが、ベストはjQuery+Velocityで今のところはいいじゃないか、とも。本末転倒ですかそうですか。

本日は以上です。

## 参考

* [最近のJS&CSSアニメーションライブラリまとめ](http://qiita.com/tejitak/items/de7fbed9bf22b9037526)
* [アニメーション最強のVelocity.jsの使い方](http://qiita.com/kyota/items/754e0e6cb7a144eda850)
* [Understanding the CSS animation-fill-mode Property](http://www.sitepoint.com/understanding-css-animation-fill-mode-property/)