Framer Motionを使ってReactでアニメーションを快適に作成する

# はじめに
簡単に優れたUIのコンポーネントを作成することができるということでコンポーネントライブラリを用いることがよくあります。そんなコンポーネントライブラリのうち[Chakra UI](https://chakra-ui.com/)を導入するときに必要になった[Framer Motion](https://www.framer.com/motion/)というライブラリについて解説します。

:::note info
この記事はReactについての知識があれば読み進める内容となっています。Chakra UIは使用しません。
:::

# Framer Motion
Framer MotionはReact用のアニメーションのライブラリです。シンプルかつ強固に宣言的な記法ができます。宣言的な面がReactの思想とも合っていて使い心地が非常に良いライブラリとなっています。

https://codesandbox.io/embed/framer-motion-nzuydi?fontsize=14&hidenavigation=1&theme=dark&view=preview

上記の実装のために必要なコードはこれだけです。タップしたりホバーした時に設定したアニメーションが動作します。
```tsx
<motion.div
  whileHover={{ scale: 1.2, rotate: 90 }}
  whileTap={{
    scale: 0.8,
    rotate: -90,
    borderRadius: "100%"
  }}
/>
```
## 利用方法
パッケージは以下のようにインストールして利用します。
```shell
npm i framer-motion
pnpm add framer-motion
```
### 基本的な使い方
Framer Motionはモーションコンポーネントを主としたライブラリです。このコンポーネントはFramer Motionによるアニメーション機能が備わったHTML要素のように扱えます。
例えば`<motion.div />`は、`<div>`にFramer Motionによるアニメーション機能が備わったと考えて操作することが出来ます。
```tsx
import { motion } from 'framer-motion';

const SampleMotion = (): JSX.Element => (
  <motion.div />
);
```

### 基本のアニメーション
Framer Motionによるアニメーション機能は`animate`に値を設定して付与することが出来ます。
例えば`animate`に以下のように設定されているとします
```tsx
<motion.div animate={{ x: 0 }} />
```
その後、何らかのきっかけで以下のようになった場合はこのコンポーネントが横軸に100px移動するアニメーションが画面に表示されます。
```tsx
<motion.div animate={{ x: 100 }} />
```
![画面収録_2023-01-31_18_01_42_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/949c25e3-02ce-8722-8706-413c0618da20.gif)
アニメーションの振る舞いは`transition`で細かく定義することが出来ます。
例えば単振動のように振る舞うをするアニメーションは以下のように作れます。
```tsx
<motion.div animate={{ x: 100 }} transition={{ type: 'spring', damping: 0 }} />
```
![画面収録_2023-01-31_18_37_24_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/50856629-10e2-5e74-f04b-a1370bab02c4.gif)
`type: 'spring'`でバネのように動くように指定して、`damping: 0`で減衰を0に設定しています。
設定できる他の値については[こちら](https://www.framer.com/motion/transition/)を参照してください。

### ジェスチャー
ホバーやタップ、ドラッグなどのユーザーの動作に合わせたアニメーションを設定することが出来ます。
ホバーは`whileHover`で、タップは`whileTap`のように`while+動作`に値を設定してモーションコンポーネントにその動作に合わせたアニメーションを付与します。
ホバーした時にサイズを4倍にするようなアニメーションは以下のように書きます(サイズのスケールは一次元の倍率なので2に設定します)。
```tsx
<motion.div whileHover={{ scale: 2 }} />
```
![画面収録_2023-01-31_19_09_14_AdobeExpress (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/591069/6373f3a1-2996-447a-3a2e-d364a6c21783.gif)
`whileHover`などに渡せる設定値は`animate`に渡せる値と同じです。

### スクロール
スクロールに関するアニメーションはスクロールに連動したものと画面(ビューポート)に要素が現れると発生するものの二種類があります。
スクロールに連動したアニメーションは`useScroll`を用いて作成します。`useScroll`はピクセル単位のスクロールの絶対的な位置を表す`scrollX`と`scrollY`と0~1で定義されたスクロール位置を返す`scrollXProgress`と`scrollYProgress`の4つの値をオブジェクトとして返します。
```tsx
const { scrollYProgress } = useScroll();

return (
  <motion.div style={{ scaleX: scrollYProgress }} />
);
```

https://codesandbox.io/embed/framer-motion01-t4f3f6?fontsize=14&hidenavigation=1&theme=dark&view=preview

これらの返り値はReactで管理された状態ではないので、変化しても再レンダリングは起きません。そのため`useEffect`や`useMemo`などの第二引数の一部として渡したとしても想定通りの動作をしません。そのため、変化した時に何らかの処理を実行したり変化を監視したい場合は`useMotionValueEvent`を用いる必要が有ります。例えば、縦軸のスクロールの進捗が変わるたびに実行したい場合は以下のように書けます。
```tsx
const { scrollYProgress } = useScroll();

useMotionValueEvent(scrollYProgress, "change", (latest) => {
  console.log(latest);
});
```
また、`useScroll`の返り値は`useSpring`というhooksを用いてバネのような増加方法を実現することもできます。
```tsx
const { scrollYProgress } = useScroll();
const scaleX = useSpring(scrollYProgress);

return <motion.div style={{ scaleX }} />;
```
続いてスクロールによって起きるアニメーションです。このアニメーションは`whereInView`を用いて行います。初期条件が必要だったら`initial`を用いて設定します。
```tsx
<motion.div
  initial={{ opacity: 0 }}
  whereInView={{ opacity: 1 }}
/>
```
ジェスチャーに似ていますが、スクロールという動作と考えると妥当ですね。このアニメーションは`viewport`で詳細を設定することが出来ます。基本的にはモーションコンポーネントが画面内に入った時に指定したアニメーションが動作しますが、指定したコンポーネント内に現れた時にアニメーションを動かすためには`viewport`内の`root`を設定する必要があります。
```tsx
const scrollRef = useRef(null)
  
return (
  <div ref={scrollRef} style={{ overflow: "scroll" }}>
    <motion.div
      initial={{ opacity: 0 }}
      whileInView={{ opacity: 1 }}
      viewport={{ root: scrollRef }}
    />
  </div>
);
```

# さいごに
Framer Motionについて、基本的な使い方を紹介しました。これまでCSSを用いたアニメーションは苦手としていましたが、このライブラリを用いて作成するのは楽しくできそうだなという印象を持ちました。
このライブラリはかなり豊富な機能があり全てを紹介しきれませんでしたが、他の機能もとても使いやすいのでドキュメントなどを参照しつつ利用してみてはいかがでしょうか。