FLASHer御用達のserial/parallelをTweenMax(JS)で

元FLASHerで、現HTML5erないしWebGLerの荒井です。

FLASHerあるあるの
「なんでHTML5/JavaScriptには○○が無いんだ！」
に時折悩まされながらも、代替手段を模索しながら日々お仕事をしています。

で、そのあるあるのひとつに
**「JavaScriptの有名なトゥイーンライブラリにはなんでserial/parallelが無いんだ！」**
というのがありました。

# serial / parallelって？

比較的最近のFLASHerがお世話になったであろう[Tween24](http://package.a24.cat/tween24/)の例だとこんな感じです。

```as3
// 1.と2.を同時に実行
Tween24.parallel(

  // 1. ここは順番に実行
  Tween24.serial(
    Tween24.tween(box1, 0.7, Ease24._6_ExpoInOut).x(400),
    Tween24.tween(box2, 0.9, Ease24._6_ExpoInOut).x(400)
  ),

  // 2. ここは0.5秒待ってから同時に実行
  Tween24.parallel(
    Tween24.tween(box3, 0.7, Ease24._6_ExpoInOut).x(400),
    Tween24.tween(box4, 0.9, Ease24._6_ExpoInOut).x(400)
  ).delay(0.5)

).play();
```

で、これまた色んな人がお世話になったであろう[BetweenAS3](http://www.libspark.org/wiki/BetweenAS3/en)の場合はこんな感じです。
※あまり使った事がなかったのと、めちゃくちゃ久々に書いたので合ってるか自信ないかも・・・

```as3
BetweenAS3.parallel(

  BetweenAS3.serial(
    BetweenAS3.to(box1, {x: 400}, 0.7, Expo.easeInOut),
    BetweenAS3.to(box2, {x: 400}, 0.9, Expo.easeInOut)
  ),

  BetweenAS3.delay(
    BetweenAS3.parallel(
      BetweenAS3.to(box3, {x: 400}, 0.7, Expo.easeInOut),
      BetweenAS3.to(box4, {x: 400}, 0.9, Expo.easeInOut)
    )
  , 0.5)

).play();
```

こんな感じで`serial/parallel`を組み合わせつつ、それらをまとめて`delay`でタイミング調整するのが基本スタイルです。
**時系列がパッと見で分かりやすいですし、複数のトゥイーンをまとめて扱える事で個々のトゥイーンを調整した時に他のトゥイーンへの影響をなるべく少なくする工夫がありますね。**
1つのトゥイーンの時間を変えたら、後のトゥイーンも全部変えなきゃ・・・みたいな事を毎回やってると開発のテンポが悪くなります。

# JavaScriptでもserial/parallelしたい
`serial`については`.to().to()・・・`な感じのメソッドチェーンで実装されているのも含めるとAS/JSに限らず結構多いのですが、`parallel`を備えてるトゥイーンライブラリってJSだとあまり見かけません。
Haxe製であれば[TweenX](http://tweenx.spheresofa.net/)というのがあるみたいで、ちょっと試したことはないのですが、JSでも使えるとは思います。

何はともあれ、これをJSでもやりたくて、過去には自前でトゥーインライブラリ（というか非同期処理のコマンドパターンライブラリみたいなの）を作ってみたりしてたんですが、**最近は[GSAP(TweenMax/TimlineMax)](https://greensock.com/gsap)を使いつつ、serial/parallelできるラッパーライブラリを自作して使っています。**

※ちなみに補足しておくと、CSSのtransition/animationもよく使います。この辺はケースバイケースですね。

さて、「ラッパーライブラリを自作して」とか得意気に言っちゃいましたが、実際は凄くシンプルです。

```js
var TimelinePlus = {};

// ---------- serial
TimelinePlus.seri = function() {
  var i, len, tl;

  tl = new TimelineMax();

  len = arguments.length;
  for (i=0;i<len;i++) {
    tl.add(arguments[i]);
  }
  return tl;
}

// ---------- parallel
TimelinePlus.para = function() {
  // argumentsを直接addしたら、なんかエラー出たのでコピー
  var args = Array.prototype.slice.call(arguments, 0);

  return new TimelineMax().add(args);
}
```
これだけ。

※ `serial/parallel`って長ったらしいので、`seri/para`に短縮してます。

`seri/para`の中でTimelineMaxのインスタンスを作り、引数で受け取ったものを`add`で突っ込んでから返すだけです。
元々、TimelineMax自体がとても高機能なのでちょっと工夫するだけで済みました。

先ほどの例を書き直すとこんな感じ。

```js
var spans = document.querySelectorAll("span.hoge");

TimelinePlus.para(
  TimelinePlus.seri(
    TweenMax.to(spans[0], 0.7, { x: 400, ease: Expo.easeInOut }),
    TweenMax.to(spans[1], 0.9, { x: 400, ease: Expo.easeInOut })
  ),
    
  TimelinePlus.para(
    TweenMax.to(spans[2], 0.7, { x: 400, ease: Expo.easeInOut }),
    TweenMax.to(spans[3], 0.9, { x: 400, ease: Expo.easeInOut })
  ).delay(0.5)
);
```

serial処理は元からあるTimelineMaxの`.to()`などを繋いでいくスタイルでも大丈夫ですが、`seri/para`で統一しておくとそれらの切り替えがサクッとできて便利なんですよね。

一応、[CodePen](http://codepen.io/yuichiroharai/pen/ZBXmMW)にもサンプルを置いてあります。
もし良かったら参考にしてみてください。

# 注意点
`seri/para`に`TweenMax.set()`を突っ込む時は`immediateRender`を`false`にしておかないと、直ちに実行されてしまうようです。

```js
var span = document.querySelector("span.hoge");

TimelinePlus.seri(

  // immediateRenderはデフォルトのtrueでOK、一瞬チラつくのを回避
  TweenMax.set(span, { x:400 }),

  TweenMax.to(span, 0.7, { x: 0, ease: Expo.easeInOut }).delay(0.2)
  
  // immediateRenderをfalseにしておけば、直ちに実行されない
  // なので、上の.toが終わってから実行される
  TweenMax.set(spans, { x:400, immediateRender:false }),

  TweenMax.to(span, 0.7, { x: 0, ease: Expo.easeInOut }).delay(0.2)
);
```

この辺りはTimelineMaxの仕様に依存しているのでご注意ください。

# さいごに

駆け足でしたが以上です。

こんな感じで他にも便利なメソッドをちょいちょい追加して秘伝のタレ化していくと良いと思います。

例えばですが、`addClass`や`removeClass`なんてメソッドを用意しておけば、任意のタイミングでCSSのtransition/animationを発動させるのが簡単になりますよね。

それでは、良きserial/parallelライフを！