【コード例付き】ReactとFramer Motionで簡単に作る！アニメーション付きインフォグラフィックUI

みなさんこんにちは、Sakuです。
今回は、ReactとFramer Motionを使用して簡単に
「**アニメーション付きのインフォグラフィックUI**」を作成していこうと思います。

今回作るものをチラ見せ（全然チラッとじゃない...）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624902/e651a594-6d1b-3225-89e2-d70d828ad885.gif" alt="最終的な制作物のプレビュー" loading="lazy">

:::note
本記事の主な対象者は以下になります。
- Webアニメーションに興味がある方
- インフォグラフィックを作ってみたい方
- Motionを使って何か作りたい方
- 分析ツールを作りたい方
- そこのあなた
:::

上記に当てはまっていなくとも構いません、本記事は私のモノ作り欲`99%`でお送りしていきますので、付き合って下さる物好きな方はぜひご一読ください:bow_tone1:

## 今回出てくる概念の説明
### Reactとは
https://react.dev/

今回はReactを使用していきます。Reactとは以下5点の特徴を備えたJavaScriptのライブラリです。（フレームワークと称されることもあります）
特に昨今のWebサイトやアプリのフロントエンド開発で使用されていて、柔軟性が非常に高い技術になります。

- **コンポーネントベースのアーキテクチャ**
    - 再利用や保守性に優れた部品でアプリやサイトを構築していく思想
- **仮想DOM**
    - 変更があった差分だけ反映する仕様
- **JSX構文**
    - JavaScript+XMLで見た目はHTMLのような感じで書ける構文
- **単方向のデータフロー**
    - データの流れが予測可能で管理しやすい単方向のデータフロー
- **巨大なエコシステム**
    - 世界で多数のユーザーが利用しており、ライブラリやドキュメントが豊富

ここでは細かく説明は省きますが、詳細を知りたい方はぜひ[公式ドキュメント](https://react.dev/learn)をご覧ください。


### Rechartsとは
https://recharts.org/en-US/

Rechartsは、以下3点の特徴を備えたデータ可視化ライブラリです。簡単に美しいグラフを作成することができます。

- **Reactアプリケーション向けに設計**
    - コンポーネント単位で使いやすい
- **SVGで描画**
    - レスポンシブデザインにしやすい
- **カスタムしやすい**
    - 様々なおプロパティが用意されており、カスタマイズしやすい
- **安定のエコシステム**
    - Chart.jsほどではないが、ユーザーが多く使用例多数

#### 基本的な使い方
Rechartを使用するには①~③の手順で使用できます。
①ライブラリをインストール
②必要なコンポーネントをインポート
③データを渡してチャートを描画

以下は基本的な例です。

```Barchart.tsx
import { BarChart, Bar } from 'recharts';

const data = [
  { name: 'Page A', uv: 4000, pv: 2400, amt: 2400 },
  { name: 'Page B', uv: 3000, pv: 1398, amt: 2210 },
];

<BarChart width={600} height={300} data={data}>
  <Bar dataKey="pv" fill="#8884d8" />
</BarChart>
```

**頻出のプロパティ・用語**
- **Bar**: 棒グラフを描画するためのコンポーネント
- **charts**: 様々な種類のグラフを描画するためのコンポーネントの総称
- **Line**: 折れ線グラフを描画するためのコンポーネント
- **Pie**: 円グラフを描画するためのコンポーネント

### Framer Motionとは
https://motion.dev/
:::note
MotionとFramerについて詳しくは[こちら]（https://motion.dev/blog/framer-motion-is-now-independent-introducing-motion）をご覧ください。

今回インストールするツールはframer-motionの為、Framer Motionと呼んでいきます。

私の理解だと、現状はFramer MotionというReact用のライブラリとMotionというVanilla APIを利用した2つが存在していて、今後は後者の方を強化しReact以外の広い環境下で使用出来るようにしていくようです。
:::

Framer Motionは、Reactアプリケーションにアニメーションを追加するためのライブラリです。直感的なAPIを提供し、複雑なアニメーションを簡単に実装できる点が特徴です。

Framer Motionは、Framerというデザインツールの一部として開発され、Reactのためのアニメーションライブラリとして独立しました。デザインと開発の橋渡しをすることを目的としているそうです。

Framer Motionは以下特徴を持ちます。
- **簡単なアニメーションの実装**
    - 簡単なコードで複雑なアニメーションを実現できる
- **パフォーマンス**
    - 高速なアニメーションを提供し、ユーザー体験を向上させる
- **直感的なAPI**
    - 開発者が使いやすいように設計されている

#### 基本的な使い方
Framer Motionを使用するには、まずライブラリをインストールし、motionコンポーネントを使用します。以下は基本的な例です。

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

<motion.div animate={{ x: 100 }}>
  Hello World
</motion.div>
```
#### 頻出のCSS知識
- **animation**: アニメーションの定義
- **opacity**: 要素の透明度
- **transition**: 状態の変化に伴うアニメーションの設定
- **transform**: 要素の位置やサイズを変更するためのプロパティ
- **keyframes**: アニメーションの各ステップを定義するためのルール
- **hover**: マウスオーバー時のスタイルを定義するための擬似クラス


### インフォグラフィックとは
![FireShot Capture 154 - 採用情報 - Studio株式会社(Studio, Inc.) - studio.inc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624902/a8195c50-b2d8-e1a0-951e-b8c77e509a26.png)
<sub>Studioさんの採用サイトから引用<sub>

インフォグラフィックは、情報やデータを視覚的に表現する手法です。複雑な情報を簡潔に伝えるために、図表やイラストを使用します。作成するメリットとしては以下が挙げられます。

- **視覚的な理解**: 情報を視覚的に表現することで、理解しやすくなる
- **注意を引く**: デザインが魅力的であれば、視聴者の注意を引きやすい
- **情報の整理**: 複雑なデータを整理し、要点を明確にする


## 実際に作成する
今回は、2種類のグラフと数字のカウントアニメーション付きで作ってみます。
スタイリングはそこまでしないので、Studioさんのモノのように垢抜けはしませんがご了承ください笑

手順は以下になります。

1. **ViteでReactのプロジェクトを立ち上げる**
2. **各種インストール**
    - motion
    - recharts
    - tailwindcss
3. **コンポーネントの作成**

### ViteでReactのプロジェクトを立ち上げてnpmをインストールする
ビルドツールのViteでReactテンプレートを選択しプロジェクトを立ち上げる。
そのプロジェクト内でnpm installを行い、node_modulesを入れる。
```bash
npm create vite@latest my-dashboard -- --template react
cd my-dashboard
npm install
```
これでプロジェクトテンプレートの作成完了です。

### 各種インストール
```bash
# framer-motionとrechartsのインストール
npm install framer-motion recharts

# TailwindCSSのインストール
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
TailwindCSSはインストール後にconfigファイルを以下のようにしてください。
```tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
```
その上で、index.cssも以下で上書きしてください。
```index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

これでインストールの終了です。

### コンポーネントの作成
実際にコンポーネントを作成していきます。

#### 棒グラフ
1. まずRechartsライブラリから必要なコンポーネント（BarChart、Bar、軸など）をインポートし、データを受け取るコンポーネントを作成
2. グラフの枠組みとして、ResponsiveContainerの中にBarChartを配置し、データをpropsで渡す
3. グラフの要素として、CartesianGrid（グリッド線）、XAxis/YAxis（軸）、Tooltip（ホバー時の詳細表示）、Bar（実際の棒グラフ）を追加

以上で、アニメーション付きの基本的な棒グラフの完成です〜
```BarChart.jsx
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { motion } from 'framer-motion';

const BarChartComponent = ({ data }) => {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.8 }}
      className="w-full h-[400px]"
    >
      <ResponsiveContainer>
        <BarChart data={data}>
          <CartesianGrid strokeDasharray="3 3" />
          <XAxis dataKey="name" />
          <YAxis />
          <Tooltip />
          <Bar
            dataKey="value"
            fill="#8884d8"
            animationBegin={0}
            animationDuration={1500}
          />
        </BarChart>
      </ResponsiveContainer>
    </motion.div>
  );
};
```

#### 円グラフ
1. Rechartsから必要なコンポーネント（PieChart、Pie、Cell、Tooltip）をインポートし、色の配列（COLORS）を定義
2. ResponsiveContainerの中にPieChartを配置し、その中心にPieコンポーネントを配置（cx、cyで中心位置を指定）
3. データの各要素に対してCellコンポーネントを使って異なる色を適用し、Tooltipでホバー時の詳細表示を追加

以上で、拡大アニメーション付きの円グラフの完成です〜

```PieChart.jsx
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip } from "recharts";
import { motion } from "framer-motion";

const PieChartComponent = ({ data }) => {
  const COLORS = ["#0088FE", "#00C49F", "#FFBB28"];

  return (
    <motion.div
      initial={{ scale: 0 }}
      animate={{ scale: 1 }}
      transition={{ duration: 0.5, ease: "easeOut" }}
      className="w-full h-[400px]"
    >
      <ResponsiveContainer width="100%" height="100%">
        <PieChart>
          <Pie
            data={data}
            cx="50%"
            cy="50%"
            fill="#8884d8"
            dataKey="value"
          >
            {data.map((entry, index) => (
              <Cell
                key={`cell-${index}`}
                fill={COLORS[index % COLORS.length]}
              />
            ))}
          </Pie>
          <Tooltip />
        </PieChart>
      </ResponsiveContainer>
    </motion.div>
  );
};

export default PieChartComponent;
```

#### アニメーション付きカウンター
1. framer-motionからモーション関連のフック（useMotionValue、useTransform）をインポートし、アニメーション用の値を初期化
2. useMotionValueで数値の変化を管理し、useTransformで小数点を丸める処理を設定
3. useEffectで実際のアニメーションを制御し、countの値をendValueまで2秒かけて変化させる

以上で、滑らかにカウントアップするアニメーション付きテキストの完成です〜

```AnimatedCounter.jsx
import { useEffect } from "react";
import { motion, useMotionValue, useTransform, animate } from "framer-motion";

const AnimatedCounter = ({ endValue }) => {
  const count = useMotionValue(0);
  const rounded = useTransform(count, Math.round);

  useEffect(() => {
    const controls = animate(count, endValue, {
      duration: 2,
      ease: "easeOut",
      onComplete: () => {
        console.log("Animation completed");
      },
    });

    return controls.stop;
  }, [endValue]);

  return (
    <motion.div className="text-6xl text-blue-600 font-bold">
      {rounded}
    </motion.div>
  );
};

export default AnimatedCounter;
```

#### ダッシュボード
上記までの3コンポーネントを作成した上で、以下の手順で作成

1. 必要なコンポーネント（BarChart、PieChart、AnimatedCounter）をインポートし、データの2. 初期状態をuseStateで設定
3. 画面をグリッドレイアウトで3列に分割し、各コンポーネントを白背景のカード内に配置
4. 各カードにframer-motionのアニメーション効果（フェードイン + 上からスライド）を追加し、delayを設定して順番に表示

以上で、各コンポーネントを含んだ、アニメーション付きダッシュボードの完成です〜
```Dashboard.jsx
import { useState } from "react";
import { motion } from "framer-motion";
import BarChartComponent from "./ui/Barchart";
import PieChartComponent from "./ui/PieChart";
import AnimatedCounter from "./ui/AnimatedCounter";

const Dashboard = () => {
  const [data, setData] = useState({
    barData: [
      { name: "2021", value: 400 },
      { name: "2022", value: 600 },
      { name: "2023", value: 800 },
    ],
    pieData: [
      { name: "Product A", value: 400 },
      { name: "Product B", value: 300 },
      { name: "Product C", value: 300 },
    ],
  });

  return (
    <div className="container mx-auto p-8 max-w-7xl">
      <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
        <motion.div
          className="bg-white p-6 rounded-lg shadow-lg max-w-md"
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5 }}
        >
          <h2 className="text-2xl mb-4">売上推移</h2>
          <BarChartComponent data={data.barData} />
        </motion.div>

        <motion.div
          className="bg-white p-6 rounded-lg shadow-lg max-w-md"
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, delay: 0.2 }}
        >
          <h2 className="text-2xl mb-4">製品別売上比率</h2>
          <PieChartComponent data={data.pieData} />
        </motion.div>

        <motion.div
          className="bg-white p-6 rounded-lg shadow-lg max-w-md"
          initial={{ opacity: 0, y: 20 }}
          animate={{ opacity: 1, y: 0 }}
          transition={{ duration: 0.5, delay: 0.4 }}
        >
          <h2 className="text-2xl mb-4">従業員数</h2>
          <AnimatedCounter endValue={1234} />
        </motion.div>
      </div>
    </div>
  );
};

export default Dashboard;
```

出来上がったDashboardをApp.jsxで読み込めば〜〜〜なんと〜〜〜次の章へどうぞ笑
```App.jsx
import Dashboard from "./components/Dashboard";

export default function App() {
  return (
    <>
      <Dashboard />
    </>
  );
}
```

## 最終的な制作物
そうして最終的にできたものが以下になります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624902/e651a594-6d1b-3225-89e2-d70d828ad885.gif" alt="最終的な制作物のプレビュー" loading="lazy">

リポジトリは[こちら](https://github.com/yusakunagatani/Infographic-demo)

当初codesandboxで作成していたため、不要なディレクトリ等が多々あったり、TailwindCSSの読み込み方法（リポジトリの方はサボってCDNです）が違ったりします。基本的な使用技術は以下になります。

**使用技術**
- Vite
- React
- TailwindCSS
- Recharts
- Framer Motion

**注意点**

:::note warn
実際に使用する場合は、以下ら辺を考慮してパフォーマンス改善を行う必要があるかもしれないです。
- 不要なコンポーネントはimportしない
- 場合によっては、LottieやGifアニメーションなども検討する
- dynamic importを使用する
- build.rollupOptions.output.manualChunksを使用する
- Rechart（svgアニメーション）ではなく、chart.js（canvasアニメーション）など他のライブラリを使用する
- useEffectを多用しすぎないようにする
- TypeScriptで型付けをする
:::

## まとめ
今回はReactとFramerMotionを使用して、簡単にアニメーション付きのインフォグラフィックを作成してみました。全然2つのツールだけではなく、多数のツールを使ったので面食らった方もいらっしゃるかもですが、いかがでしたでしょうか？

私的にアニメーション付きのインフォグラフィックというのは何故かスマートに見えていた為、今回デモとはいえ作成できてとても楽しかったです！

ぜひ作ってみたい方の参考になれば幸いです。

本記事を読んでいただきありがとうございました！:point_up:

---

**関連サイト**
https://ja.react.dev/
https://recharts.org/en-US/
https://motion.dev/
https://tailwindcss.com/