Framer Motion＋MUIで、麻雀の点数表示ができる　Reactアプリを作ってみた

<!--
title: Framer Motionを使って麻雀の点数表示ができるReactアプリを作ってみた
tags:  React, framer-motion, MUI, react-hook-form, zod
-->

# 1. はじめに

[NRI OpenStandia Advent Calendar 2022](hReactttps://qiita.com/advent-calendar/2022/nri-openstandia)の 12 日目は、React 用のアニメーションライブラリである Framer Motionの基本的な使い方をご紹介します！
また、ReactのUIコンポーネントライブラリであるMUIとFramer Motionを一緒に使うときの実装方法についても話します！

# 2. Framer Motion とは
今回の記事のメインとなるFramer Motionとは何者でしょうか。

公式サイトの紹介文を和訳すると「React 用のオープンソースのプロダクション対応モーションライブラリ」とのこと。
つまり、React で使用できるアニメーションライブラリです。
デザインツール[Framer](https://www.framer.com/)を開発しているFramer社が提供しています。
要素にプロパティを設定して直接アニメーションの挙動を制御できます。

https://www.framer.com/motion/

# 3. やりたいこと

Framer Motion を使ってアプリを作りたいと思います。
アプリで使う技術の前提条件としては

- 言語は TypeScript で、[React](https://ja.reactjs.org/)、画面 UI は[MUI](https://mui.com/)を使う。
- もちろん<font color="red">Framer Motion</font>を使ってアニメーションをつける。（うるさいくらいに多用する）
- 入力ページにはバリデーションも実装したいので、[React Hook Form](https://react-hook-form.com/)と[zod](https://zod.dev)も使う。

です。

# 4. Framer Motionの使用方法
アプリの実装の前にFramer Motionの使い方を見ていきます。

#### インストール

```
npm install framer-motion
```

#### 基本の書き方
もとになる要素はこれです。
```tsx
<motion.div />
```

このモーションコンポーネントに基本的なアニメーション機能を追加していきます。
```tsx
import { motion } from 'framer-motion'
export const AnimateSample = () => (
    <motion.div
      style={{ width: 300, height: 300, backgroundColor: 'blue' }}
      animate={{ x: 1000 }} // x方向に1000移動
    />
)
```
`animate={{ x: 1000 }}`とすることで、このように対象となるdivをx方向に1000だけ移動させることができます。

![sample1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/e849af26-0e3f-6bf1-1fad-7446060b0dc7.gif)

`animate`で対象物をどのように動かすかを設定できます。
他にも`initial`でアニメーション開始時の状態、`exit`で終了時の状態を設定できます。
不透明度を表す`opacity`を用いて、開始時と終了時は`initial={{ opacity: 0 }}`、アニメーションは`animate={{ opacity: 1 }}`のように設定できます。


続いて、マウスホバーをイベントとしたアニメーションを設定してみます。

```tsx
import { motion } from 'framer-motion'
export const HoverSample = () => (
    <motion.div
      style={{ width: 300, height: 300, backgroundColor: 'blue' }}
      whileHover={{ scale: 1.2 }} // ホバー時に大きさ1.2倍
    />
)
```
` whileHover={{ scale: 1.2 }}`とすると、マウスホバーしたら大きさを1.2倍にする、というアニメーションを書けます。
`whileHover`でマウスホバー時どうするかを設定できます。`scale`は大きさの変化です。

![sample2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/4e1a4f0f-1f3f-9930-3453-a8fefde85b39.gif)

`whileHover`の他にも以下のようなイベントリスナーがあります。

|  項目名  |  説明  |
| ---- | ---- |
|  whileHover  |  ホバージェスチャが認識されている間にアニメーション化するプロパティ  |
|  whileTap  |  コンポーネントが押されている間にアニメーション化するプロパティ  |
|  whileFocus  |  フォーカス ジェスチャが認識されている間にアニメーション化するプロパティ  |
|  whileDrag  |  ドラッグジェスチャが認識されている間にアニメーション化するプロパティ  |
|  whileInView  |   要素が表示されている間にアニメーション化するプロパティ |


次に親要素の中に複数の子要素を持つ場合のアニメーション設定を見ていきます。

```tsx
import { motion } from 'framer-motion'

const list = {
  visible: {
    opacity: 1, // 不透明度1
    transition: {
      staggerChildren: 1, // 子要素に1秒間隔でアニメーションを行う
    },
  },
  hidden: {
    opacity: 0, // 不透明度0
  },
}

const item = {
  visible: { x: 0 },
  hidden: { x: -100 },
}

export const VariantsSample = () => (
    <motion.ul initial="hidden" animate="visible" variants={list}>
      <motion.li variants={item}> いち</motion.li>
      <motion.li variants={item}> に</motion.li>
      <motion.li variants={item}> さん</motion.li>
    </motion.ul>
)
```

`variants`を使用すると、親要素にだけ`initial`や`animate`を書けば、子要素含めたサブツリー全体をアニメーション化できます。
手順としては以下です。
- 親要素に`initial`の状態名（ここでは"hidden"）と`animate`の状態名（ここでは"visible"）を書く。
- コンポーネントの外に親要素と子要素それぞれのアニメーションを設定する。
- `variants`で要素にアニメーションを指定する。

親要素の`animate`に指定した`staggerChildren: 1`とは、子要素を1秒間隔で表示させるというものです。
表示するとこうなります。
![sample3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/45a2603e-fcae-2576-16cb-96b42c7198b4.gif)

Framer Motionには、他にもhooksや3Dアニメーションなどの機能があります。

# 5. Framer MotionとMUIをコラボさせる
[4. Framer Motionの使用方法](#4-framer-motionの使用方法)で表示したアニメーションと同じものをMUIを使って書いてみようと思います。

まず、特に何も調査せずに「`<motion.div>`でMUIのコンポーネントを囲めば動くのでは？」と思い書いてみました。こんな感じです。
```tsx
import { Box } from '@mui/material'
import { motion } from 'framer-motion'

export const AnimateMuiSample = () => (
  <motion.div animate={{ x: 1000 }}> {/* Box要素をmotion.divで囲む */}
    <Box sx={{ width: 300, height: 300, backgroundColor: 'blue'}} />
  </motion.div>
)
```
表示すると
![sample4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/d867a11a-62b3-5119-708f-8f85f5fc10ea.gif)
のようになりました。
MUIを使わないで書いた[4. Framer Motionの使用方法](#4-framer-motionの使用方法)のアニメーションと同じように見えます。

続いて、マウスホバー時のアニメーションも書いてみます。
```tsx
import { Box } from '@mui/material'
import { motion } from 'framer-motion'

export const HoverMuiIncorrectSample = () => (
  <motion.div whileHover={{ scale: 1.2 }}> {/* Box要素をmotion.divで囲む */}
    <Box sx={{ width: 300, height: 300, backgroundColor: 'blue'}} />
  </motion.div>
)
```
![sample5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/a4413c1c-25c1-a29d-b02a-40533bc02dc5.gif)
マウスホバーをBoxの横から外に出したときは元に戻らない、、？
[4. Framer Motionの使用方法](#4-framer-motionの使用方法)とは違う挙動になってしまったようです。

このような挙動となってしまった理由としては、
`<motion.div>`の部分でdiv要素にmotionをつけていて、Box自体にはmotionをつけていないからです。よって、div内かつBox外では、motion的にはホバーされている状態と把握されているのです。
ブラウザの開発者ツールで確認すると、motionをつけたdivの部分がBoxより横に大きいことが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/5e23f1bd-6c03-b2eb-6e70-8dcf6994f970.png)
下の図は要素にマウスホバーした時のスクリーンショットですが、上の図と比較すると、motionをつけたdiv要素の`transform`の値が変化していることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/7de9d49f-7551-d4ca-5d2a-10c65854f56a.png)

Boxの外に出たら`scale`を戻すという、[4. Framer Motionの使用方法](#4-framer-motionの使用方法)と同じアニメーションにする実装方法はこちらです。
```tsx
import { Box } from '@mui/material'
import { motion } from 'framer-motion'

export const HoverMuiCorrectSample1 = () => (
    <Box
      component={motion.div} {/* Box要素のcomponentにmotion.divを設定 */}
      whileHover={{ scale: 1.2 }}
      sx={{ width: 300, height: 300, backgroundColor: 'blue' }}
    />
)
```
![sample6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/543ccf4c-7f4d-f0dd-7a36-a9779fee405e.gif)
MUIコンポーネントのBox要素の`component`に`motion.div`を指定して、動きはBox要素に直接設定します。

別の実装方法として
```tsx
import { Box } from '@mui/material'
import { motion } from 'framer-motion'

export const HoverMuiCorrectSample2 = () => {
 const MuiMotionBox = motion(Box)  // motionの引数にBoxを指定
 return(
    <MuiMotionBox
      whileHover={{ scale: 1.2 }}
      sx={{ width: 300, height: 300, backgroundColor: 'blue' }}
    />
 )
} 
```
ともできますが、今回は`component={{motion.div}}`の方法にしたいと思います。

ちなみに、上記は[Box](https://mui.com/material-ui/react-box/)要素の場合を記載しましたが、
- [Button](https://mui.com/material-ui/react-button/)要素の場合は`component={motion.button}`
- [Typography](https://mui.com/material-ui/react-typography/)要素の場合は`component={motion.p}`

と書きます。

# 6. Framer Motionを使って、麻雀の点数表示アプリ作ってみる

Framer Motionを使ったアプリを作りたいけどアプリの題材どうしよう、、と迷った挙句、麻雀の点数表示アプリを作ることにしました。
理由は単純で、私がどうしても点数計算を覚えられないからです。。。（3900 点とかがややこしくさせてくるから覚えられない）
アプリの要件としては

- インプットは
  - 何翻
  - 何符
  - 和了がロン or ツモ
  - 和了の人が親 or 子
- アウトプットは点数。
- ツモの場合は和了以外の人が 1 人いくら払うかまで表示する。

とします。

#### まずは、入力画面！
完成イメージはこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/4961a76c-35d7-4696-8620-a43d14df52ba.png)

最初に背景と文字を作っていきます。

```tsx
  <Box
    component={motion.img}
    animate={{ x: 0 }}
    initial={{ x: -500 }}
    transition={{
      duration: 0.5,  // 0.5秒間でアニメーション
    }}
    sx={{
      width: '100%',
      minHeight: '100vh',
    }}
    alt="majan"
    src={MahjongImage1}
    position="absolute"
  />
```
背景は、Boxコンポーネントで`animate`と`initial`を指定しました。開始地点であるx方向-500の位置からx方向0の位置に移動させます。
また、`transition`を`duration: 0.5`と設定することによって、0.5秒間で移動させるようにしています。
こんな感じになります。
![sample7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/5685de1e-f505-978e-54ae-42a3bdd2dcab.gif)

文字を入れていきます。
```tsx
  <Grid
    container
    component={motion.div}
    animate={{
      x: [0, -200, -100],  // 一連のアニメーションをフレームとして設定
      y: [0, 200, 100],
    }}
    transition={{
      duration: 0.5,
    }}
    alignItems="center"
    justifyContent="center"
    sx={{
      position: 'absolute',
    }}
  >
    <Box sx={{ backgroundColor: 'primary.dark', width: '2000px' }} textAlign="center">
      <Typography variant="h1" color="common.white">
        Mahjong Score Calculation
      </Typography>
    </Box>
  </Grid>
  <Grid
    container
    component={motion.div}
    animate={{
      x: [0, -200, -100],  // 一連のアニメーションをフレームとして設定
      y: [0, 300, 220], 
    }}
    transition={{
      duration: 0.5,
    }}
    alignItems="center"
    justifyContent="center"
    sx={{
      position: 'absolute',
    }}
  >
    <Box sx={{ backgroundColor: 'primary.dark', width: '600px' }} textAlign="center">
      <Typography variant="h5" color="common.white">
        Enter "han" and "fu" to see your score!
      </Typography>
    </Box>
  </Grid>
```
`animate`の値を配列で書くことによって一連のアニメーションをフレームとして設定しています。これにより、各値が順番にアニメーション化されます。
x方向でいうと最初は0の位置、次に-200の位置、最後に-100の位置と移動します。
y方向でいうと最初は0の位置、次に200の位置、最後に100です。
`duration: 0.5`により、一連の動きが0.5秒で動くようにしています。
![sample8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/2b5bc2a4-236b-0381-6875-0635ddcf4860.gif)


次に翻と符を入力するPaperの中身を書いていきます。

```tsx
  <Paper
    component={motion.div}
    animate={{
      scale: 1, // スケールを1へ
      position: 'absolute',
      top: '30%',
      width: '30%',
      left: '10%',
    }}
    initial={{
      scale: 0,
      position: 'absolute',
      top: '30%',
      width: '30%',
      left: '10%',
    }}
    transition={{
      duration: 2, // 2秒間
    }}
    sx={{ m: 5, p: 5 }}
  >
    <Grid container alignItems="center" justifyContent="center" direction="column">
      <Typography variant="h5">翻を入力してください。</Typography>
      <Typography>※役満の場合は13と入力してください。</Typography>
      <TextField
        sx={{ mt: 2 }}
        error={!!errors.han}
        helperText={errors.han?.message}
        {...register('han', {
          setValueAs: (value?: string) =>
            value == null || value === '' ? undefined : parseInt(value, 10),
        })}
      />
      {watch('han') < 5 && watch('han') > 0 && (   // 1以上4以下の場合のみ
        <>
          <Typography variant="h5" sx={{ mt: 5 }}>
            符を入力してください。
          </Typography>
          <TextField
            sx={{ mt: 2 }}
            error={!!errors.hu}
            helperText={errors.hu?.message}
            {...register('hu', {
              setValueAs: (value?: string) =>
                value == null || value === '' ? undefined : parseInt(value, 10),
            })}
          />
        </>
      )}
    </Grid>
  </Paper>
```
Paperのアニメーションは、2秒間でPaperのscaleを0から1にして登場させるようにしています。
あとは、Framer Motionとは関係ないですが、React Hook Formの`watch`を使って、入力された翻が4以下の場合のみ符の入力フォームを出すようにしています。

同じように和了の入力をするPaperを実装していきます。
```tsx
  <Paper
    component={motion.div}
    animate={{
      scale: 1,
      position: 'absolute',
      top: '30%',
      width: '30%',
      left: '53%',
    }}
    initial={{
      scale: 0,
      position: 'absolute',
      top: '30%',
      width: '30%',
      left: '53%',
    }}
    transition={{
      duration: 2,
    }}
    sx={{ m: 5, p: 5 }}
  >
    <Grid container alignItems="center" justifyContent="center" direction="column">
      <Typography variant="h5">和了を選択してください。</Typography>
      <Controller
        name="agari"
        control={control}
        render={({ field }) => (
          <FormControl sx={{ width: '300px', mt: 2 }}>
            <Select {...field} error={!!errors.agari}> {/* Controller内でSelectを書く */}
              <MenuItem value="ron">ロン</MenuItem>
              <MenuItem value="tumo">ツモ</MenuItem>
            </Select>
            <FormHelperText error>{errors?.agari?.message}</FormHelperText>
          </FormControl>
        )}
      />
      <Typography variant="h5" sx={{ mt: 5 }}>
        和了が親か子を選択してください。
      </Typography>
      <Controller  
        name="oyako"
        control={control}
        render={({ field }) => (
          <FormControl sx={{ width: '300px', mt: 2 }}>
            <Select {...field} error={!!errors.oyako}>　
              <MenuItem value="oya">親</MenuItem>
              <MenuItem value="ko">子</MenuItem>
            </Select>
            <FormHelperText error>{errors?.oyako?.message}</FormHelperText>
          </FormControl>
        )}
      />
    </Grid>
  </Paper>
```
並んでいる2つのPaperが違う動きだと気持ち悪いので、アニメーションは同じです。
React Hook Formの`Controller`を使って`Select`で選んだ選択肢を登録していきます。
![sample9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/cf1df7e4-8e2e-b635-1a11-6d7f43d03859.gif)

最後に「点数を表示する」ボタンです。
`useAnimationFrame`というものを使います。

```tsx
export const DisplayButton = () => {
  const ref = useRef<HTMLButtonElement>(null)
  useAnimationFrame((time, delta) => {  // アニメーションループ
    if (!ref.current) return
    ref.current.style.transform = `rotateX(${delta}deg) rotateY(${time / 10}deg)`
  })

  return (
        <Button
          ref={ref}
          variant="contained"
          color="primary"
          type="submit"
          size="large"
          sx={{ position: 'absolute', top: '80%', left: '45%' }}
        >
          点数を表示する
        </Button>
  )
}
```
`useAnimationFrame`とは指定したコールバックに最新のフレーム時間を出力するアニメーションループです。アニメーションフレームごとに1回、コールバックを実行します。
第1引数である`time`はコールバックが最初に呼び出されてからの合計時間、第2引数である`delta`は最後のアニメーションフレームからの合計時間です。
![sample10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/d7c82e80-d24a-a4d2-6c0b-8cf9c51f5e7a.gif)
アニメーションをつけたことによって、表示ボタンがすごく押しにくくなりました。笑

#### 続いて、結果出力画面！
完成イメージはこちら。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/29bd9098-2cf3-dbb0-8110-7cc7cb9bd7a4.png)

まず、Framer Motionを使う画面表示ではなく、結果の点数を出す処理の部分を実装していきます。
以下コードの`value`は前の画面で入力したデータを指します。
`context`を使用してデータを渡していますが、ここでは実装を省略します。

```tsx
  // 13翻より大きい場合は13翻とする。valueがfalseの場合は0翻とする。
  if (value && value.han > 13) {
    han = 13
  } else {
    han = value ? value.han : 0
  }

 // 和了の人がもらえる点数と、その他の人が払う点数を計算
  // 和了が親の場合
  if (value?.oyako === 'oya') {
    totalScore = OyaPointCalculation(han, value.hu)
    // ツモの場合は、子供1人あたりの支払う額も算出
    if (value?.agari === 'tumo') {
      koPayment = OyaTumoPointCalculation(han, value.hu, totalScore)
    }
  // 和了が子の場合
  } else if (value?.oyako === 'ko') {
    totalScore = KoPointCalculation(han, value.hu)
    // ツモの場合は、親と子それぞれの1人あたりの支払う額も算出
    if (value?.agari === 'tumo') {
      [oyaPayment, koPayment] = KoTumoPointCalculation(han, value.hu, totalScore)
    }
  }
```
もらえる点数と、ツモの場合の親子それぞれ１人が支払う点数を計算します。
`OyaPointCalculation`、`KoPointCalculation`、`OyaTumoPointCalculation`、`KoTumoPointCalculation`は翻と符、点数を与えると結果を返してくれる関数です。
関数の定義部分の実装は省略します。


続いて、表示する画面を作っていきます。
表示するアウトプットは、和了の人がもらえる点数と、支払い点数（ツモの場合のみ）です。
点数表示部分を書いていきます。
```tsx
  <Paper sx={{ m: 5, p: 5 }}>
    <Typography variant="h2" color="#AFC87D" textAlign="center">
      結果は...
    </Typography>
    <Grid container alignItems="center" sx={{ mt: 6 }}>
      <Grid
        item
        xs={7}  // 12の横の長さのうち7
        component={motion.div}
        animate={{ scale: 1 }}
        initial={{ scale: 0 }}
        transition={{ delay: 2 }}
        sx={{ pr: '2%' }}
      >
        <Typography variant="h1" textAlign="right" color="#004F2A" sx={{ mb: 10 }}>
          {totalScore}点
        </Typography>
        {value?.agari === 'tumo' && (
          <>
            {value?.oyako === 'ko' && (
              <Typography variant="h4" textAlign="right" sx={{ mb: 1 }}>
                親の支払いは{oyaPayment}点
              </Typography>
            )}
            <Typography variant="h4" textAlign="right">
              子の支払いは{koPayment}点
            </Typography>
          </>
        )}
      </Grid>
    </Grid>
  </Paper>
```
表示するとこうなりました。
右に赤ドラの絵を入れたいので、[Grid](https://mui.com/material-ui/react-grid/)はxsで指定しています。
![sample11.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/9716adb7-9c64-e71a-7938-7677b2ee58c3.gif)

赤ドラを表示していきます。`useTransform`と`useTime`を使います。
```tsx
export const Dora = () => {
  const time = useTime  // 1フレームごとに毎回更新されるモーション値を返す
  const rotate = useTransform(time, [0, 4000], [0, 360], { clamp: false }) // 4秒で360度回転
  return(
    <Grid item xs={5} textAlign="right" sx={{ mb: 20, pr: 8 }}>    {/* 12の横の長さのうち5 */}
      <motion.img alt="dora" src={DoraImage} width="50%" style={{ rotate }} />
    </Grid>
  )
}
```

`useTransform`は片方が動いている間に別のアニメーションを実行します。
つまりここでは、`time`が`[0,4000]`すなわち4秒の間に、対象物を`[0, 360]`つまり360度回転させるとなります。
` { clamp: false }`は永続的にその2つの動きをマッピングする、ということです。

また、`useTime`は永続的なアニメーションを作りたいときに便利です。
モーション値が最初に作成されてから、1フレームごとに毎回更新されるモーション値を返します。
ここでいうモーション値とは、アニメーション値の状態と速度を追跡しているものです。
Framer Motionの全てのモーションコンポーネントは、内部でモーション値`MotionValues`を使用して、アニメーション値の状態と速度を追跡します。
![sample12.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/6b72e3b7-b85d-6faa-83f3-50c290588782.gif)


最後に、戻るボタンを実装して出来上がりです。
```tsx
  <Grid container alignItems="center" direction="column">
    <Button
      component={motion.button}
      initial={{ x: -200 }}
      animate={{ x: 200 }}
      transition={{            // transitionに項目設定
        type: 'spring',
        repeat: Infinity,
        repeatType: 'mirror',
        repeatDelay: 0.1,
      }}
      whileHover={{ scale: 2 }}  // ホバーしたら大きさを倍にする
      variant="contained"
      color="secondary"
      type="submit"
      size="large"
      onClick={() => {
        back()
      }}
    >
      戻る
    </Button>
  </Grid>

```
`transition`に`type`、`repeat`、`repeatType`、`repeatDelay`を設定しています。
それぞれの項目の説明は以下の通りです。

|  設定項目  |  値  |  説明  |
| ---- | ---- |---- |
|  type  |  spring  | バネのような自然な動きのあるアニメーションタイプ |
|  repeat  |  Infinity  | アニメーションを永続的にする |
| repeatType  |  mirror | 順方向→逆方向→再び順方向で実行される動き |
| repeatDelay  |  0.1 | 繰り返しの動きの間を`0.1`秒間止める |


![sample13.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/697416/a06b4777-813f-2a39-e0b8-851cfe9b94d0.gif)



#### 完成！

<iframe width="560" height="315" src="https://www.youtube.com/embed/33Jn7wejAxQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>



# 7. 感想

- Framer Motionを使うと思っていたよりもアニメーションを簡単に直感的に書くことができました。
- Framer Motionにはこれ以外にもたくさん機能があるので深いですね。3Dアニメーションまでありました。

https://www.framer.com/docs/three-introduction/

- [React Router](https://reactrouter.com/en/main)とも連携できるらしいので、今後試してみたいです。
- Framerと組み合わせて、デザインツールでデザインしたもののコード化も出来るようなので、そちらも試そうと思います。
- （符計算も覚えられないので、インプット画面に何符ではなく条件を入力できるようにしたいです。）

# 8. さいごに
Framer MotionとMUIを使ったReactアプリをご紹介しました。
レスポンシブ化できておらず、画面の位置調整が下手な部分が多くありますが、多めに見てください。
もっと良い実装方法等ありましたら、コメントいただけると幸いです。

# 9. 参考リンク

https://dev.classmethod.jp/articles/play-framer-motion-basic/

https://zenn.dev/chot/articles/ce72f1835bc1a7

https://framerbook.com/