framer-motionを使って超簡単に無料のSVGアイコンをちょっとだけオシャレにする方法

# どこにでもある無料のsvgアイコンで可能
大体のSVGアイコンに使えるアニメーションなので、是非、使って欲しい技
下記のように線で書くようにアイコンが表示されてコンタクトフォームが表示される部分
https://x.com/Nao8epicmotion/status/1786287666176409892

# 動作環境及び使用ライブラリ
React18, framer-motion
```react
{
  "name": "starter",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "dependencies": {
    "@emailjs/browser": "^4.3.3",
    "framer-motion": "^11.0.23",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-icons": "^5.0.1",
    "sass": "^1.72.0"
  },
  "devDependencies": {
    "@types/react": "18.2.15",
    "@types/react-dom": "18.2.7",
    "@vitejs/plugin-react": "4.0.3",
    "eslint": "8.45.0",
    "eslint-plugin-react": "7.32.2",
    "eslint-plugin-react-hooks": "4.6.0",
    "eslint-plugin-react-refresh": "0.4.3",
    "vite": "4.4.5"
  }
}

```

アニメーションさせてるContactコンポーネントのコード

```react
import React, { useRef, useState } from 'react'
import "./Contact.scss"
import { motion, useInView } from "framer-motion"
import emailjs from "@emailjs/browser";

const variants = {
    initial: {
        y: 500,
        opacity: 0
    },
    animate: {
        y: 0,
        opacity: 1,
        transition: {
            duration: 0.5,
            staggerChildren: 0.1
        }
    },
}

const Contact = () => {

    const ref = useRef(null);
    const formRef = useRef(null);
    const [error, setError] = useState(null);
    const [success, setSuccess] = useState(null);


    const isInView = useInView(ref, { margin: "-100px" })
    const sendEmail = (e) => {
        e.preventDefault();

        emailjs
            .sendForm('service_mnlw8oi', 'template_x4nfmrw', formRef.current, {
                publicKey: 'r48m0uYsCIYJ_ye1q',
            })
            .then(
                (result) => {
                    setSuccess(true);
                    console.log(result);
                },
                (error) => {
                    setError(true);
                    console.log(error);
                },
            );
    };


    return (
        <motion.div className='contact'
            ref={ref}
            variants={variants}
            initial="initial"
            whileInView="animate">
            <motion.div className='text-container' variants={variants}>
                <motion.h1 variants={variants}>Let's Work Together</motion.h1>
                <motion.div className="item" variants={variants}>
                    <h2>メールアドレス</h2>
                    <span>yamagata_7580@yahoo.co.jp</span>
                </motion.div>
                <motion.div className="item" variants={variants}>
                    <h2>所在地</h2>
                    <span>東京都港区麻布十番7-7-7</span>
                </motion.div>
                <motion.div className="item" variants={variants}>
                    <h2>電話番号</h2>
                    <span>03-1234-5678</span>
                </motion.div>
            </motion.div>
            <div className='form-container'>
                <motion.div className="svgIcon"
                    initial={{ opacity: 1 }}
                    whileInView={{ opacity: 0 }}
                    transition={{ delay: 3, duration: 1 }}
                >
                    <svg
                        width="400px"
                        height="400px"
                        viewBox="0 0 64 64">
                        <motion.path
                            initial={{ pathLength: 0 }}
                            animate={isInView && { pathLength: 1 }}
                            transition={{ duration: 18 }}
                            fill="none" strokeWidth={0.2}
                            d="省略">
                        </motion.path>
                    </svg>
                </motion.div>
                <motion.form
                    ref={formRef}
                    initial={{ opacity: 0 }}
                    whileInView={{ opacity: 1 }}
                    transition={{ delay: 4, duration: 1 }}
                    onSubmit={sendEmail}
                >
                    <input type="text" placeholder='お名前' name="name" />
                    <input type="email" placeholder='メールアドレス' name="email" />
                    <input type="text" placeholder='タイトル' name="subject" />
                    <textarea cols="30" rows="8" placeholder='お問い合わせ内容' name="message" />
                    <motion.button whileHover={{ background: "gray", color: "white" }}>送信する</motion.button>
                    {error && "メールの送信に失敗しました"}
                    {success && "メールの送信に成功しました"}
                </motion.form>
            </div>
        </motion.div>
    )
}

export default Contact

```

# 解説
## useInViewとuseRefを組み合わせて使用
useInViewとuseRefを組み合わせて使用すれば、特定の要素がビューポート内に表示されているかどうかを簡単に検出できます。useInViewは、指定されたDOM要素がビューポート内に表示されているかどうかを監視し、その状態を返します。この場合、refにuseInViewを渡すことでビューポート内に表示されているかどうかを検出してます。

## 基本的なframer-motionの使用方法

参考Document
https://www.npmjs.com/package/framer-motion

とりあえずインストールとインポート
`npm install framer-motion`
`import { motion, useInView } from "framer-motion"`
※さきに説明したuseInViewもframer-motionから使います


動作自体は以下に書いてある通りですが、variantsで初期値とアニメーション値をそれぞれ設定して、あとはアニメーションさせたい部分の要素に`motion.`を使って完成です。
svgアイコン部分ではvariantsは使用してないように見えるかもしれませんが、親クラスのcontact部分で使用しています。
whileInViewは文字通り、ビューポート内に表示される特定の要素に対して、ビューポート内に表示されている間にアニメーションを実行させます。
```react
<motion.div className='contact'
            ref={ref}
            variants={variants}
            initial="initial"
            whileInView="animate">
            
                ~ 省略 ~
    
</motion.div>
```


```react
const variants = {
    initial: {
        y: 500,
        opacity: 0
    },
    animate: {
        y: 0,
        opacity: 1,
        transition: {
            duration: 0.5,
            staggerChildren: 0.1
        }
    },
}
```

## アイコン部分のアニメーション
本題のSVGアイコン部分はpathの部分にmotion.を加えて`motion.path`とします。
なぜならSVGの線の部分を0→1にさせたいだけなので、言われてみればロジックはメチャクチャ簡単ですよね。
variantは親のcontactクラスに設定しているので、内包されているこの部分では`initial,animate,transition`と変化させたい値を実装するだけで可能になります。

```react
<svg
    width="400px"
    height="400px"
    viewBox="0 0 64 64">
    <motion.path
        initial={{ pathLength: 0 }}
        animate={isInView && { pathLength: 1 }}
        transition={{ duration: 18 }}
        fill="none" strokeWidth={0.2}
        d="省略">
    </motion.path>
</svg>
```

# 流れをまとめる
1. useInViewとuseRefでビューポート内に要素が入ってきたかどうかを検出させて、
2. 要素が入ってきたら、svgアイコンの線が ( pathLength: 0→pathLength: 1 )になるだけで、動画のように徐々にアイコンが描かれる形になります