Reactでアニメーションやインタラクションをサクサク動かすTips

## 概要

Reactを用いたWebアプリケーションにおけるアニメーションやインタラクションの実装について個人的なメモ。
これがベストプラクティスであるとは断言できないが、自分なりに試行錯誤した末の結論をまとめておく。



## React/Redux特有のパフォーマンス的な問題を解決

* React/Reduxは確かに便利で高パフォーマンスだが、リアルタイムなインタラクションには弱い
* リアルタイムなインタラクションとは `onMouseMove` や `onDrag` `onScroll` などのイベントで発火する動作
* 上記のようなイベントを扱うケースでは `state` や `props` を使ってはいけない（カクカクしたりぎこちない動きになる）
* 代わりに、状態管理にはJSの変数やプロパティなどを使うようにするとスムーズに動く
* とはいえオレオレフレームワーク化しがちなので、そういった実装は最小限に留める



## stateやpropsを使わずにアニメーションさせるには

* refで取得したDOM要素を直接操作する



## render()やcomponentWillReceiveProps()等の中で計算処理をしない

* `_.find()`や`moment().isSame()`といったよく使われる処理を、renderの中で配列で繰り返すと重い
* render以外の場所（reduxならactionCreatorやreducer等）でやるべき
* util化して別ファイルから読み込んだとしてもrender()で実行するなら結局意味がないのでNG
* render()やcomponentWillReceiveProps()は静的な値を使うだけにするのがベスト



## ReactでCSSアニメーションをカンタンに扱える公式ライブラリ

* [react-transition-group](https://reactcommunity.org/react-transition-group/) というReact公式ライブラリを使うと平和が訪れる
* 簡単な使い方解説

```html
<Transition in={trueOrFalse} timeout={500}>
  {(state)=>(
    <div className={classNames(styles.foobar, styles[state])}>
      ほげほげ
    </div>
  )}
</Transition>
```

* **in** `(boolean)` in属性にtrueを代入すると、引数stateがenteringに変わる
* **timeout** `(number)` CSS Transitionのduration（アニメーションの長さ）と同義。
* **state** `(string) ` アニメーションの状態
    - `"entering"` in==falseからtrueへのアニメーション中
    - `"entered"` in==falseからtrueへのアニメーション完了後
    - `"exiting"` in==trueからfalseへのアニメーション中
    - `"exited"` in==trueからfalseへのアニメーション完了後



## 気持ちよいアニメーションを実現させるバウンスやイージングのコツ

画像を載せるのが面倒なので雰囲気で解説

```css
/* スッキリシンプルなイージング */
.easing1 {transition-timing-function: ease}

/* 今風な気持ちいいイージング */
.easing2 {transition-timing-function: cubic-bezier(1,0,0,1)}

/* 最後に軽くバウンス */
.bounce1 {transition-timing-function: cubic-bezier(0.9,0,0,1.1)}

/* 最後に思いっきりバウンス */
.bounce2 {transition-timing-function: cubic-bezier(0.5,0,0,1.8)}
```

* もっとこだわりたい場合はこのWebツールが便利  
http://cubic-bezier.com/



## アニメーションは出来る限りCSS Transitionを使う

* Webブラウザでアニメーションさせる方法は色々ある
    1. JS（setTimeout/setInterval）
    2. SVG
    3. CSS Transition
    4. CSS Animation
    5. Canvas
    6. WebGL
    7. Animated GIF
* それぞれ長所短所がある
* 一般的にはCSS Transionを使うのが無難
* 理由①：CSSのプロパティを変更するだけでアニメーションしてくれる手軽さ
* 理由②：仕様がシンプルなので学習コストが少ない
* 理由③：react-transition-groupを使うことでアニメーション前後の挙動も制御しやすい



## アニメーションと相性の良いCSSプロパティ

* 結論から言うと、opacityかtransformだけをアニメーションさせれば幸せになれる
* それら以外のプロパティをアニメーションさせると重い（最悪の場合ブラウザが落ちる）
* ブラウザの画面描画の仕組み
    - **Layout** `(最も重い)` レイアウト処理（関連プロパティ：width, height, margin, padding, top, font-sizeなど）
    - **Paint** `(やや重い)` 描画処理（関連プロパティ：color, background, border-radius, box-shadowなど）
    - **Composite Layers** `(軽い)` レイヤー合成処理（関連プロパティ：opacity, transformなど）
* Layout → Paint → Composite Layersの順番で処理が実行される
* つまり、
* Layout系プロパティを変更すると、LayoutとPaintとComposite Layersの再計算が実行されるため重い
* Paint系プロパティを変更すると、PaintとComposite Layersの再計算が実行されるため少し重い
* Composite Layers系プロパティを変更すると、Composite Layersのみ再計算されるため軽い



## positionプロパティは取り扱い注意

* `position:absolute`を指定した要素を便宜上 **絶対座標要素** と呼ぶ
* 絶対座標要素の先祖要素の中で`position:absolute`または`position:relative`を適用された最も近い親等の要素を便宜上 **親要素** と呼ぶ
* シームレスなインタラクションを実現する上で、親要素との関係性は非常にデリケートなので最大限注意しなければならない
* また、position指定をするとレイヤを新規生成することになり、レイヤの作りすぎはパフォーマンスの低下に繋がるため十分注意する



## 要素を重ねてアニメーションさせるテクニック

* シームレスでスムーズなインタラクションを実現する上で、アニメーション対象プロパティとしてwidthやheightなどを使えない（使いたくない）シーンがある
* そういう時は変形前と変形後の要素を別々に用意し、opacityとtransformでクロスフェード（フェードインする要素とフェードアウトする要素を重ねるアニメーション）をさせると吉
* ただ、座標の計算ロジックを作る工数が地味にかかるので、多用しないほうが吉



## 検証中の項目

* iOS Safariでブラウザが落ちるケースがある
    * DOM要素が多すぎるせい？
    * ブラウザ起動直後に再現しやすい