Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

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

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

  • React/Reduxは確かに便利で高パフォーマンスだが、リアルタイムなインタラクションには弱い
  • リアルタイムなインタラクションとは onMouseMoveonDrag onScroll などのイベントで発火する動作
  • 上記のようなイベントを扱うケースでは stateprops を使ってはいけない(カクカクしたりぎこちない動きになる)
  • 代わりに、状態管理には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 というReact公式ライブラリを使うと平和が訪れる
  • 簡単な使い方解説
<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へのアニメーション完了後

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

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

/* スッキリシンプルなイージング */
.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)}

アニメーションは出来る限り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要素が多すぎるせい?
    • ブラウザ起動直後に再現しやすい
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away