Framer Motionで簡単アニメーション実装

この記事は[ラクスAdvent Calendar 2022](https://qiita.com/advent-calendar/2022/rakus) 14日目の記事です。Reactで簡単にアニメーションを実装したくてFramer Motionを試してみました。本記事は[公式ドキュメント](https://www.framer.com/docs/introduction/)の例を参考に実装してみたときの備忘録になります。

## Framer Motionとは
Framer Motionは[Framer](https://www.framer.com/)が提供するReactのためのプロダクションレディなアニメーションライブラリで、複雑なユーザーインタラクションをシンプルなマークアップで表現することができます。

このライブラリのコアとなるのが`<motion.div>`コンポーネントです。

```
<motion.div>
```

このコンポーネントにFramer Motionのプロパティを設定することで簡単にアニメーションを表現することができます。

導入は非常に簡単で以下のコマンドを実行するだけです。

```
npm install framer-motion
```

今回は手軽にコンポーネントを実装できるChakraUIと一緒に使ってみたいと思います。

```
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
```

## Animations
`animate`プロパティには各アニメーションの値をオブジェクトとして渡すことができます。

```tsx
<motion.div animate={{ x: 100 }} />
```

これらの値が変化するとモーションコンポーネントは自動的に新しいアニメーションを生成します。
![animate.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612354/e3fdbcb2-ec77-d5cc-3dbe-dc37f769e528.gif)

```tsx
const [sliderValue, setSliderValue] = useState(0);

return (
<Box gap={4} p={8}>
  <motion.div animate={{ x: sliderValue + "%" }}>
    <Box w={16} h={16} bg="tomato" />
  </motion.div>
  <Slider
    mt={4}
    aria-label="slider-ex-1"
    onChange={(val) => setSliderValue(val)}
  >
    <SliderTrack>
      <SliderFilledTrack />
    </SliderTrack>
    <SliderThumb />
  </Slider>
</Box>
);
```

また`initial`プロパティと`transition`プロパティを使ってアニメーションの初期状態や速度を制御することができます。
![initial.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612354/222f6e89-ba03-d6c2-d6b0-e07a997f8ffd.gif)


```tsx
<motion.div
  initial={{ opacity: 0, scale: 0.5 }}
  animate={{ opacity: 1, scale: 1 }}
  transition={{ duration: 0.5 }}
>
  <Text
    bgGradient="linear(to-l, #7928CA, #FF0080)"
    bgClip="text"
    fontSize="6xl"
    fontWeight="extrabold"
  >
    Welcome to Framer Motion
  </Text>
</motion.div>
```

## Keyframes
値を配列で設定するとMotionコンポーネントはその値を順番にアニメイトしてくれます。デフォルトでは、各キームレームは等間隔のタイミングで制御されますが、`transition`プロパティによってより詳細にタイミングやイージングを設定することができます。

<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612354/82592f50-9dde-8acf-9b15-ac302f391859.gif" />

```tsx
<motion.div
  animate={{
    scale: [1, 2, 2, 1, 1],
    rotate: [0, 0, 180, 180, 0],
  }}
  transition={{
    duration: 2,
    ease: "easeInOut",
    times: [0, 0.2, 0.5, 0.8, 1],
    repeat: Infinity,
    repeatDelay: 1,
  }}
>
  <Box bgGradient="linear(to-r, green.200, pink.500)" w={16} h={16} />
</motion.div>
```

## Gesture animations
Motionコンポーネントは`whileHover`、`whileTap`、 `whileDrag`、`whileFocus`といったプロパティも提供しています。これらはユーザーのアクションにしたがってコンポーネントに一時的なアニメーションを付与してくれます。

以下のようにホバー時とタップ時のアニメーションを同時に設定した場合、Motionは自動的に２つのアニメーションの相互作用をハンドリングします。例えばコンポーネントがホバージェスチャーを行っている最中にタップされたとき、Motionはタップ時のアニメーションを優先してアニメイトするように制御してくれます。

```tsx
<motion.div whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
  <Button>Button</Button>
</motion.div>
```

<img width="200px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612354/30c571e7-a802-27e0-be30-21a15bd53ae8.gif" />

## Drag
`drag`プロパティを使用することでコンポーネントをドラッグすることができるようになります。さらに`dragConstraints`を設定することでコンポーネントの配置を特定の範囲に制限することができます。制限する範囲はピクセルで定義するか他のDOM要素へのrefでも定義できます。アニメーションは弾性的な動きで表現され、この弾性の強さは`dragElastic`プロパティで設定できます。

<img width="300px" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/612354/70bd8057-d568-2307-a1f3-78667ad6dc95.gif" />

```tsx
<Box w="128px" h="128px" borderRadius={8} bgColor="gray.200">
  <motion.div
    drag
    dragConstraints={{
      top: 0,
      left: 0,
      right: 64,
      bottom: 64,
    }}
    dragElastic={0.5}
  >
    <Box w={16} h={16} bg="tomato" borderRadius={8} />
  </motion.div>
</Box>
```

## おわりに
Framer Motionを使うことでとても簡単にアニメーションを実装することができました。今回紹介した機能はframer motionのほんの一部でしかありません。この他にも様々なプロパティやhooksが提供されているので興味を持った方はぜひ[公式ドキュメント](https://www.framer.com/docs/introduction/)を参照してみてください！

## 参考文献
https://www.framer.com/docs/introduction/

https://chakra-ui.com/