LoginSignup
0
0

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

Last updated at Posted at 2024-05-03

どこにでもある無料のsvgアイコンで可能

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

動作環境及び使用ライブラリ

React18, framer-motion

{
  "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コンポーネントのコード

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は文字通り、ビューポート内に表示される特定の要素に対して、ビューポート内に表示されている間にアニメーションを実行させます。

<motion.div className='contact'
            ref={ref}
            variants={variants}
            initial="initial"
            whileInView="animate">
            
                ~ 省略 ~
    
</motion.div>
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と変化させたい値を実装するだけで可能になります。

<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 )になるだけで、動画のように徐々にアイコンが描かれる形になります
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0