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.jsそのままラップしてるので、DOM依存していた。
結論
velocity-reactが求めるものに一番近かったが、DOM依存なのが痛い。
しかし何かを諦めなければいけないので、今回はDOM依存を諦めた。
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>
原理がわかってしまえばあたり前だけど、親要素をはずすのは無理ということが判明。
ということでその辺りは用法を理解して使った方が良い。