どこにでもある無料の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>
流れをまとめる
- useInViewとuseRefでビューポート内に要素が入ってきたかどうかを検出させて、
- 要素が入ってきたら、svgアイコンの線が ( pathLength: 0→pathLength: 1 )になるだけで、動画のように徐々にアイコンが描かれる形になります