React・実務の道具立て

動きをつける(Motion)

CSSで足りるところと、Reactだとどうしても書けないところを分けます。「消えるときのアニメーション」をAnimatePresenceで解決し、並び替え・ページ遷移・動きを減らす設定まで扱います。

見た目の仕上げの話をします。ただ、この回で扱いたいのはReactだから必要になる部分だけです。

まずCSSで足りる

押したときの色の変化、出てくるときのふわっと——このあたりはCSSのままで動きます。Reactにしたからといって書き方は変わりません。

<div className="transition hover:scale-105 hover:shadow-md">

Tailwindならtransitionanimate-*がそのまま使えます。まずここで足りないかを考えるのが順番です。

Reactだと書けないこと——消えるとき

困るのは消えるときです。

{isOpen && <div className="transition-opacity">メッセージ</div>}

isOpenfalseになった瞬間、Reactは要素をDOMから消します。消えたものにアニメーションはかけられません。「ふわっと出るのに、消えるときはパッと消える」——これがReactでよくある片手落ちです。

自分で書くなら「消すのを300ミリ秒待ってから消す」という管理が要ります。これを引き受けてくれるのがアニメーションライブラリです。

npm install --save-exact motion

Motion(旧Framer Motion)を使います。

import { AnimatePresence, motion } from 'motion/react'

<AnimatePresence>
  {items.map((item) => (
    <motion.li
      key={item.id}
      initial={{ opacity: 0, x: -8 }}   // 出る前
      animate={{ opacity: 1, x: 0 }}    // ふだん
      exit={{ opacity: 0, x: 8 }}       // 消えるとき
    >
      {item.name}
    </motion.li>
  ))}
</AnimatePresence>

<motion.li>は「アニメーションできる<li>」です。divでもbuttonでも同じように書けます。

並び替えの動き

layoutを1つ足すと、位置が変わったときに勝手に移動アニメーションが付きます

<motion.li layout key={item.id}>

検索でカードが絞りこまれる、お気に入りが上に来る——残ったカードがスッと動くのはこれです。自分でCSSで書こうとすると、移動前後の座標を測る処理が要ります。

ページの切りかえ

react-routerを使っているなら、View Transitionsという標準の仕組みに乗るのが手軽です。

<Link to={`/birds/${bird.id}`} viewTransition>

viewTransitionを付けるだけで、ページの切りかえがふわっと繋がります(対応していないブラウザでは何も起きません=そのまま切りかわるだけ)。

細かく制御したいときは、CSSで名前を付けます。

::view-transition-old(root) { animation-duration: 200ms; }

動きを減らす設定に従う

OSには「視差効果を減らす」(Reduce Motion)という設定があります。動きで具合が悪くなる人のための設定なので、無視してはいけません。

import { useReducedMotion } from 'motion/react'

function BirdCard() {
  const shouldReduceMotion = useReducedMotion()

  return (
    <motion.div
      animate={{ opacity: 1 }}
      transition={{ duration: shouldReduceMotion ? 0 : 0.3 }}
    >

useReducedMotion()は、その設定が有効ならtrueを返します。動きを消すか、うんと短くするのが対応です。

CSSだけで書いている部分は、メディアクエリで同じことをします。

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}

入れる前に考えること

やりたいこと手段
ホバー・押したときの変化CSS(Tailwindのtransition
出てくるときのふわっとCSSでも書ける
消えるときの動きAnimatePresence
並び替え・移動layout
ページの切りかえView TransitionsviewTransition
ドラッグ・複雑な連鎖Motionなどのライブラリ

ライブラリを入れればその分ファイルは大きくなります。「消えるときの動き」が要らないなら、入れないほうが速い——という判断もふつうにあります。読みこむ量が気になるなら、motion/reactから使うものだけを取り込む形にして、公開の回で見たビルド後のサイズを実際に見比べてください。

べんりワザコピペで使えるWebアニメーション集CSSだけで作れる動きの見本。まずここで足りないかを確かめる用に

公式ドキュメントも見てみる

Motion外部サイトAnimatePresence | React exit animations | Motion for ReactMotion公式のAnimatePresence。「消えるときのアニメーション」を扱うページで、keyが必要な理由もここに書かれています(英語)

このレッスンのまとめ

  1. ホバーや出現はCSSのままで足りる。まずそこを確かめる
  2. Reactで書けないのは消えるとき——Reactが先に要素を消してしまうから
  3. AnimatePresenceが要素を預かり、動きが終わってから消す。keyが必須
  4. layoutを1つ足すと並び替えの移動が付く
  5. ページの切りかえはviewTransition(対応していないブラウザでは何も起きない)
  6. 動かすのはtransformopacityだけ。widthtopはカクつく
  7. useReducedMotion()で「動きを減らす」設定に従う。CSS側はprefers-reduced-motion
  8. 目安は200〜300ミリ秒。全部を動かさない

次は、コードの置き場所と分け方です。

やってみよう:追加と削除に動きをつける

ことりずかんのカードに動きを足します。

  1. npm install --save-exact motion
  2. カードの一覧を<AnimatePresence>で包み、<div><motion.div>にする
  3. initialanimateexitを書いて、追加したときと削除したときの動きを見る
  4. layoutを足して、検索でしぼりこんだときに残ったカードが動くのを見る
  5. useReducedMotion()で、動きを減らす設定のときはdurationを0にする
<AnimatePresence>
  {shownBirds.map((bird) => (
    <motion.div
      key={bird.id}
      layout
      initial={{ opacity: 0, scale: 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{ opacity: 0, scale: 0.95 }}
      transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
    >
      <BirdCard bird={bird} />
    </motion.div>
  ))}
</AnimatePresence>

3番で、<AnimatePresence>を外すとどうなるかも見てください。追加は動くのに削除だけパッと消える——それがこの回の出発点です。

よくある質問

CSSのtransitionではだめですか?
出るときの動きや、押したときの動きはCSSで足ります。困るのは消えるときで、Reactが要素をDOMから外してしまうとCSSのアニメーションは走りません。そこだけライブラリの出番です。
Motionとreact-springはどちらを使いますか?
新しく入れるならMotion(旧Framer Motion)が情報量でも書きやすさでも有利です。既存の案件でreact-springが使われていれば、そのまま使って構いません。
アニメーションを入れると重くなりませんか?
transformとopacityだけを動かす分にはほとんど負荷になりません。widthやtopのように配置の計算をやり直させるプロパティを動かすと重くなります。ここはCSSと同じ話です。
動きが苦手な利用者への配慮は必要ですか?
必要です。OSに「視差効果を減らす」設定があり、これを有効にしている人には動きを止めるか短くします。ReactではuseReducedMotionで読み取れます。