Framer motion
About Framer Motion
- 리액트 컴포넌트 기반의 재사용 가능한 애니메이션 추가 가능하다.
- motion.div 등의 wrapper 태그로 작성
- initial -> animate -> exit ( transition 애니메이션 지속 시간 )
- AnimatePresence는 컴포넌트가 제거될 때 exit 애니메이션을 실행할 수 있게 해줍니다.
- *없는경우라면 바로 언마운트 된다.
eg) opacity 0->1, 1->0
import { motion, AnimatePresence } from 'framer-motion';
<div>
<button onClick={() => setIsOpen(!isOpen)}>MotionEg01 Toggle</button>
{/* AnimatePresence는 컴포넌트가 제거될 때 exit 애니메이션을 실행할 수 있게 해줍니다 */}
<AnimatePresence>
{isOpen && (
<motion.div
className="h-[100px] w-[400px] top-0 bg-zinc-900/50"
// 초기 상태 - 완전히 투명
initial={{ opacity: 0 }}
// 나타날 때 - 완전히 불투명
animate={{ opacity: 1 }}
// 사라질 때 - 다시 투명하게
exit={{ opacity: 0 }}
// 애니메이션 지속 시간 1초
transition={{ duration: 1 }}
>
AnimatePresence!
</motion.div>
)}
</AnimatePresence>
</div>
eg) 오른쪽에서 나타나기, 뒤로 작아지면서 사라지기
<AnimatePresence>
{isOpen && (
<motion.div
className="h-[100px] w-[400px] top-0 bg-zinc-900/50"
initial={{ opacity: 0, x: 10, scale: 1 }}
// 오른쪽에서 나타나기
animate={{
opacity: 1, // 완전히 보이게
x: 0, // 원래 위치로
scale: 1, // 원래 크기로
transition: {
delay: 0.2, // 0.2초 후에 시작
type: "spring", // 스프링 효과 사용
stiffness: 200, // 스프링의 강도
damping: 30, // 스프링의 감쇠
},
}}
// 뒤로 작아지면서 사라지기
exit={{
opacity: 0, // 완전히 투명하게
x: 0, // x축 위치 유지
scale: 0.95, // 약간 작아지면서
transition: { delay: 0 }, // 즉시 시작
}}
>
AnimatePresence!
</motion.div>
)}
</AnimatePresence>
eg) 서서히 전체화면 채우기 -> 작아지는 박스로 사라지기
"use client";
import React, { useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useWindowSize } from "usehooks-ts";
const MotionEg03 = () => {
const [isOpen, setIsOpen] = useState(false);
const { width: windowWidth, height: windowHeight } = useWindowSize();
const isMobile = windowWidth ? windowWidth < 768 : false;
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>MotionEg03 Toggle</button>
{/* AnimatePresence는 컴포넌트가 제거될 때 exit 애니메이션을 실행할 수 있게 해줍니다 */}
<AnimatePresence>
{isOpen && (
<motion.div
className="absolute top-0 right-0 bg-zinc-900"
initial={{
opacity: 0,
scale: 1,
width: windowWidth,
height: windowHeight,
}}
// 서서히 전체화면 채우기
animate={{
opacity: 1,
scale: 1,
borderRadius: 0,
transition: {
delay: 0,
type: "spring",
stiffness: 200,
damping: 30,
},
}}
// 작아지는 박스로 사라지기
exit={{
opacity: 0,
scale: 0.5,
transition: {
delay: 0.1,
type: "spring",
stiffness: 600,
damping: 30,
},
}}
>
<button onClick={() => setIsOpen(!isOpen)}>Close</button>
<div>AnimatePresence!</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
export default MotionEg03;
eg) 격자형 구조, 1번부터 아래에서 톡톡 튀어오르기
<div className="grid sm:grid-cols-2 gap-2 w-full">
{suggestedActions.map((suggestedAction, index) => (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ delay: 0.05 * index }}
key={`suggested-action-${suggestedAction.title}-${index}`}
className={index > 1 ? "hidden sm:block" : "block"}
>
<Button
variant="ghost"
onClick={async () => {
window.history.replaceState({}, "", `/chat/${chatId}`);
append({
role: "user",
content: suggestedAction.action,
});
}}
className="text-left border rounded-xl px-4 py-3.5 text-sm flex-1 gap-1 sm:flex-col w-full h-auto justify-start items-start"
>
<span className="font-medium">{suggestedAction.title}</span>
<span className="text-muted-foreground">
{suggestedAction.label}
</span>
</Button>
</motion.div>
))}
</div>
eg) 애니메이션 - 로딩 아이콘
Ref : https://playcode.io/framer_motion
<AnimatePresence>
<motion.div
className="w-[50px] h-[50px] bg-pink-300"
animate={{
scale: [1, 1.1, 1.1, 1, 1],
rotate: [0, 0, 180, 180, 0],
borderRadius: ["0%", "0%", "50%", "50%", "0%"],
}}
transition={{
duration: 2,
ease: "easeInOut",
times: [0, 0.2, 0.5, 0.8, 1],
repeat: Infinity,
repeatDelay: 1,
}}
/>
</AnimatePresence>