Reactのアニメーションを攻略する

  • 12
    いいね
  • 0
    コメント

Reactのアニメーションを攻略する

仕事でReactのアニメーションをするにあたり、
やりたいことを満たすために旅立った冒険記。

やりたいこと

要件

  • 要素の追加・削除時にアニメーション
  • リストではない複数要素を別々に動かす

きもち

  • カジュアルに使いたい
    要件満たすために余計な記述はしたくない。可能な限りシンプルに保ちたい。  

  • DOMに依存したくない
    Universalにしたいので、style弄りはVirtualDOM経由が原則。

  • ロジックで制御したい
    複雑化への対応や保守性を考えると、絶対JSで全てコントロールできた方がよい。

  • 汎用性の高さが欲しい
    適材適所でライブラリを使うのは嫌なので、できるだけシンプルかつ柔軟にしたい。

  • Redux、MobXは使いたくない
    Redux作者もローカルのstateでいいみたいなこと言ってるし、
    わざわざ依存するライブラリを増やしたくない。

  • CSSアニメーションは使いたくない
    CSSだとDOM依存する、ロジック制御できない、のコンボで死亡。

  • 自作したくない
    メンテしたくないので。

先行ライブラリを調査

Reactのアニメーション系ライブラリを調査。

  • react-addons-css-transition-group
    React標準のaddon。
    CSSで簡単なアニメーションをさせるときに使う。
    コンポーネントのマウント・アンマウント時にclassをつけることでアニメーションできる。
    CSSだとロジックで制御できなくてつらい。

  • react-addons-transition-group
    React標準のaddon。
    Reactのライフサイクルイベントを利用して、より自由にアニメーションを制御できる。
    コンポーネントのマウント・アンマウントをフックしてアニメーション処理を書ける。
    ローレベルAPIなのでロジックが冗長になる。

  • react-tween-state
    https://github.com/chenglou/react-tween-state
    簡単にTweenアニメーションができるが、簡単なことしかできない。
    README.mdにリンクされてるLive demoが動かない。
    動かす対象のコンポーネントにmixinでメソッドを追加して使う。
    汎用性は高くないが、その割にカジュアルさがあまりない気がする。
    mixinなので、使うには新しくコンポーネントを作る必要がある。

  • react-animate
    https://github.com/elierotenberg/react-animate
    簡単にTweenアニメーションができるが、簡単なことしかできない。
    onTickでどうにでも出来そうだけど、それでどうにかするとスメルを放ちそう。
    HoCなので、使うには新しくコンポーネントを作る必要がある。

  • react-tweenable
    https://github.com/MandarinConLaBarba/react-tweenable
    react-tween-stateをラップして、もっとカジュアルに使えるようにしたもの。
    react-tween-stateをそのまま使うよりは良いかもしれない。

  • react-imation
    https://github.com/gilbox/react-imation
    アニメーションと、それを制御する機能を提供するライブラリ。
    Timelineコンポーネントで時間を制御できるようになっており、
    それによって複雑なアニメーションにも対応できる。
    READMEを見た感じだとかなりよく出来ているのだが、機能の割に詳細に書かれていない。
    ただ資料不足を差し引いてもスターが少ないのが不思議。

  • react-motion
    https://github.com/chenglou/react-motion
    react-tween-stateと同じ作者で、もっともスターが多い。
    簡単にTweenアニメーションができ、関連ライブラリやDemoが豊富。
    API自体はシンプルながらもリストに動きを付けるのが簡単。
    しかしeasingの種類、delay、durationの指定はできない。
    強力だが、複雑なものを作るには特性を理解して工夫する必要がある。
    また「何秒でアニメーションさせてください」と細かい秒数や
    easingのさせ方が指定される場面では完全に無力。

  • react-gsap-enhancer
    https://github.com/azazdeaz/react-gsap-enhancer
    あのGreenSockをReactで使えるようにしたコンポーネント。
    ReactだとUpdateでDOM壊れるのでそれに対応するために作った(意訳)と書いてある。
    が、TweenMax自体はElementではなくただのobjectでも引数に取れるので、
    GreenSockをそのまま使えば良いのでは感がある。

  • velocity-react
    https://github.com/twitter-fabric/velocity-react
    あのVelocity.jsをReactで使えるようにしたコンポーネント。
    Twitterがメンテしていてスターも多い。
    使い方は直感的で、記述もシンプル。人気の理由がわかる気がする。

  • react-web-animation
    https://github.com/bringking/react-web-animation
    ReactでWeb Animations APIを使えるようラップしたコンポーネント。
    Web Animations APIの仕様自体は良いと思っているので使いたいが、
    VirtualDOMで作ってるのに、仕様をRealDOMに寄せるのはイケてない。

  • react-tween
    https://github.com/clari/react-tween
    簡単にTweenアニメーションができるが、簡単なことしかできない。
    I/Fがreact-motionに寄せてあり、物理的な自然な動きをさせたい場合はreact-motionを使い、
    durationやeasingを設定したい場合はreact-tweenを使ってね、ということらしい。
    が、oapcityとheightでeasingを別々に指定する、ということができない。
    またTween.TransitionGroupが要素削除時にアニメーションをつけられるが、
    削除アニメーションが終了後に要素が削除されてないといったイケてない部分が見られる。

要件を満たせるライブラリ

まず「要素の削除時にアニメーション」でだいぶ絞られてしまった。
以下の5つの方法が対応していた。

  • ReactCSSTransitionGroup
    要素の削除時にclassが与えられるので、CSSでアニメーションさせられる。
    カジュアルに使えるが、CSSなのでロジックで制御しにくくDOM依存になる。

  • ReactTransitionGroup
    要素の削除をフックして、callbackで削除処理を終了させる。
    callbackを呼ぶまで自由にDOMを変更できる。
    汎用性は高いが、ローレベルAPIなのでカジュアルに使えない。

  • react-motion
    Motion、StaggeredMotion、TransitionMotionの3つのコンポーネントがある。
    削除時アニメーションに対応しているのはTransitinMotionのみ。
    しかしリストを扱うことを対象としたコンポーネントなので、
    「リストではない複数要素を別々に動かす」という目的にハマらない。

  • react-tween
    Tween、Tween.TransitionGroupの2つのコンポーネントがある。
    削除時アニメーションに対応しているのはTween.TransitionGroupのみ。
    こちらもリストを扱うコンポーネントなので、うまくハマらない。

  • velocity-react
    VelocityComponent、VelocityTransitionGroupの2つのコンポーネントがある。
    削除時アニメーションに対応しているのはVelocityTransitionGroupのみ。
    react-motion、react-tweenと違ってリストを扱うコンポーネントではないので使いやすい。

結論

velocity-reactが自分が求めるものに一番近かったし、他の人にもオススメ。
ただ要件によって採用するものは変わってくるかなとは思うが、
要件が変わったとしても以下の3つのみ検討すれば良さそう。

  • react-motion
  • velocity-react
  • GreenSockをDOM依存しないように使う

この3つがおすすめ。

react-imatio も結構良さそうなのだが、それならGreenSockで済むという感じもある。
一度、試してみたいライブラリではある。

おまけ: 要素の削除時にアニメーション

結局長いものに巻かれる結果となり、つまらない感じになってしまった。
ただ各コンポーネントがどうやって削除するタイミングをフックしてアニメーションさせているのか気になっていたので調査した。

  • ReactCSSTransitionGroup、ReactTransitionGroup
    ReactTransitionGroupのソースを読むと、componentWillReceivePropsでpropsを乗っ取って前後のchildren状態をマージしている。このとき比較も行い、削除された要素に対してcomponentWillLeaveを実行し、callbackが呼ばれたらchildrenから削除している。

  • react-motion
    TransitionMotionのソースを読むと、削除されないようにする方法は同じくcomponentWillReceivePropsでpropsを乗っ取っている。実際に削除を行う処理は、アニメーションが終了したタイミングをよしなに判断して削除している。

  • react-tween
    Tween.TransitionGroupのソースを読むと、こちらもcomponentWillReceivePropsによるprops乗っ取り。削除はどうやっているのか確認すると、なんと削除を行っていなかった。そのためずっと要素が残り続けてしまう。おまけにアニメーション終了イベントも用意されていないので、こちらで終了をフックして削除することもできない。詰んでる。ひどい。

  • velocity-react
    VelocityTransitionGroupのソースを読むと、ReactTransitionGroupをラップしていた。

つまり、componentWillReceivePropsでpropsを乗っ取る役割を持ったコンポーネントで囲い、
中の要素にアニメーション処理をしている。
したがって、以下のように必ずコンテナを必要とするTreeをとる。

<PropsHackComponent>
  <AnimationComponent/>
  <AnimationComponent/>
  <AnimationComponent/>
<PropsHackComponent>

原理がわかってしまえばあたり前だけど、親要素をはずすのは無理ということが判明。
ということでその辺りは用法を理解して使った方が良い。