Framer Motion 애니메이션 라이브러리
애니메이션 라이브러리
설치
npm i framer-motion
div에 animation을 적용하고 싶을 때 motion에 있는 html태그를 사용해줘야한다.
import { motion } from "framer-motion";
export default function App() {
return (
<div className="App">
<motion.div></motion.div>
</div>
);
}
styled-components에서 motion을 사용해야하는 경우
// 타입선언은 괄호 뒤에 한다.
const Box = styled(motion.div)<{ bg: string}>`
....
`;
background-color를 변경해야하는 경우 red, blue가 아닌 rgba 컬러를 사용해야한다.
기본 props
initial - 애니메이션 적용하기 전 초기스타일
animate - 애니메이션 적용 스타일
transition- 트랜지션 속성
<Box transition={{ delay: 3 }} initial={{ borderRadius: "10px"}} animate={{ borderRadius: "100px" }} />
transform-origin - transition을 사용할 때 어느위치에서 애니메이션을 할지 지정할 수도있다. (기본 css 속성)
transform-origin: right center;
Variant
variant를 사용하면 코드 정리가 된다.
animation에 관련된 props속성을 분리 된 Object로 옮긴 것.
오브젝트이름, 오브젝트 내 property 이름은 제약없다.
적용할 컴포넌트에 variants props로 object이름 넣어준다.
필요한 props에 Object property 이름 넣어준다.
const myVars = {
start: { scale: 0 },
end: { scale: 1, rotateZ: 360, transition: { type: "spring", delay: 0.5 } },
};
function App() {
return (
<Wrapper>
<Box variants={myVars} initial="start" animate="end" />
</Wrapper>
);
}
부모 컴포넌트에 variants가 적용되어있으면 props 복사해서 적용해준다.
자식 컴포넌트에 따로 적용하지 않아도 된다.
아래 코드에서 Circle 컴포넌트에 initial="start" animate="end"가 자동으로 들어가있다.
<Wrapper>
<Box variants={myVars} initial="start" animate="end">
<Circle /> //initial="start" animate="end"
<Circle /> //initial="start" animate="end"
<Circle /> //initial="start" animate="end"
<Circle /> //initial="start" animate="end"
</Box>
</Wrapper>
자식에서 다른 animation을 적용하려는 경우
자식 Variants Object를 만들고 property 이름을 부모와 동일하게 해주면
자식 컴포넌트에서 variants props만 넘겨주면 된다.
const boxVars = {
start: { },
end: { },
};
const circleVars = {
start: { },
end: { },
};
function App() {
return (
<Wrapper>
<Box variants={boxVars} initial="start" animate="end">
<Circle variants={circleVars} />
<Circle variants={circleVars} />
<Circle variants={circleVars} />
<Circle variants={circleVars} />
</Box>
</Wrapper>
);
}
부모 애니메이션 속성에서 자식 애니메이션을 컨트롤 할 수 있다.
delayChildren - 자식 delay
staggerChildren - 자식들 간의 delay
const boxVars = {
start: { scale: 0 },
end: {
scale: 1,
transition: {
type: "spring",
bounce: 0.25,
delayChildren: 0.5, // 자식 delay
staggerChildren: 0.2 // 자식들의 delay
},
},
};
const circleVars = {
start: { opacity: 0, y: 10 },
end: { opacity: 1, y: 0, transition: { type: "spring" } },
};
function App() {
return (
<Wrapper>
<Box variants={boxVars} initial="start" animate="end">
<Circle variants={circleVars} />
<Circle variants={circleVars} />
<Circle variants={circleVars} />
<Circle variants={circleVars} />
</Box>
</Wrapper>
);
}
Gesture
whileHover, whileTap
마우스 호버, 클릭 시 등등 다양한 제스처에 대한 애니메이션
const boxVars = {
hover: { scale: 1.5, rotate: 180 },
click: { borderRadius: "100px" },
};
function App() {
return (
<Wrapper>
// whileHover: 마우스 호버, whileTap: 클릭
<Box variants={boxVars} whileHover="hover" whileTap="click"></Box>
</Wrapper>
);
}
drag
드래그 애니메이션
드래그 시키고 싶은경우 drag props를 넣어주면 된다. (간편-)
// drag="x" x좌표로만 드래그가능
// drag="y" y좌표로만 드래그가능
// whilDrag 드래그할 때 적용할 애니메이션
<Box drag="x" variants={boxVars} whileDrag="drag" ></Box>
drag constraint (드래그 가능한 영역 제한)
1. 수치를 넣어서 영역을 제한하는 방법
<Box
drag
variants={boxVars}
dragConstraints={{ top: -50, bottom: 50, left: -50, right: 50 }}
whileDrag="drag"
></Box>
2. dom으로 영역을 제한하는 방법
function App() {
const boxRef = useRef<HTMLDivElement>(null);
return (
<Wrapper>
<BoxWapper ref={boxRef}>
<Box
drag
variants={boxVars}
dragConstraints={boxRef}
whileDrag="drag"
></Box>
</BoxWapper>
</Wrapper>
);
}
어디로 드래그 해도 제자리로 돌아오게 하는 법
// 1. 수치로 넣어주는 방법
dragConstraints={{ top: 0, bottom: 0, left: 0, right: 0 }}
// 2. 간단한 props로 적용하는 방법
dragSnapToOrigin
드래그 할때 잡아끄는 힘 조정하기
dragElastic = {1} 잡아당기는 힘 0-1까지
motionValue
애니메이션 했을 때 계산되는 값
motionValue가 업데이트 될 때 react rendering cycle을 발동시키지 않는다.
그래서 컴포넌트는 다시 렌더링 되지 않는다.
useMotionValue
function App() {
const x = useMotionValue(0);
console.log(x); // x가 변경되도 console로그는 로드할때 한번밖에 찍히지 않는다.
return (
<Wrapper>
<Box
style={{ x: x }} // 키와 value가 같아서 x하나만 써줘도 된다.
drag="x"
variants={boxVars}
whileDrag="drag"
></Box>
</Wrapper>
);
}
값을 출력하고 싶은경우 useEffect를 사용해서 확인한다.
useEffect(() => {
x.onChange(() => {
console.log(x.get());
});
}, [x]);
useTransform
motionValue값에 따른 애니메이션을 적용할 때 사용
예를 들어
x값이 -800인 경우 scale값을 2로
x값이 0인 경우 scale값을 1로
x값이 800인 경우 scale값을 0로
useTransform(x, [-800, 0, 800], [2, 1, 0])// 어떤값, 값의 위치, output
// 값의 위치, output 길이 같아야한다
function App() {
const x = useMotionValue(0);
const scale = useTransform(x, [-400, 0, 400], [2, 1, 0]);
return (
<Wrapper>
<Box
style={{ x, scale }}
drag="x"
variants={boxVars}
whileDrag="drag"
></Box>
</Wrapper>
);
}
useScroll
scrollY - 스크롤 값
scrollProgress 스크롤값에 따라 달라지는 값 , 0-1사이의 값
SVG
fill, pathLength로 애니메이션을 적용할 수 있다.
const SvgWrapper = styled.svg`
width: 200px;
height: 200px;
path {
stroke-width: 5;
stroke: white;
}
`;
function App() {
const svgVar = {
start: { pathLength: 0, fill: "rgba(255, 255, 255, 0)" },
end: { pathLength: 1, fill: "rgba(255, 255, 255, 1)", transition: { duration: 3 } },
};
return (
<Wrapper>
<SvgWrapper xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<motion.path
variants={svgVar}
initial="start"
animate="end"
d="..."
/>
</SvgWrapper>
</Wrapper>
);
}
fill, pathLength 애니메이션을 따로 transition을 주고싶은경우 transition props를 써주고
모든 요소에 들어가는 건 default로 써주면 되고
특정요소에 들어가는건 property 이름 써주고 하면 된다.
<Wrapper>
<SvgWrapper xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<motion.path
variants={svgVar}
initial="start"
animate="end"
transition={{
default: { duration: 5 }, // 모든요소에 적용
fill: { duration: 3, delay: 2 }, // fill요소에만 적용
}}
d="..."
/>
</SvgWrapper>
</Wrapper>
animatePresence
나타나고, 사라지는 애니메이션 적용
animatePresence 컴포넌트 내부에는 display 조건문이 있어야한다.
조건문안에 animationPresence 컴포넌트가 위치하면 안된다.
exit props - element가 사라질때 적용하는 애니메이션
function App() {
const [isVisible, setVisible] = useState(false);
const toggleClick = () => {
setVisible((prev) => !prev);
};
const BoxVars = {
initial: { scale: 0, opacity: 0 },
visible: { opacity: 1, scale: 1 },
exit: { y: 50 },
};
return (
<Wrapper>
<AnimatePresence>
{isVisible ? (
<Box variants={BoxVars} initial="initial" animate="visible" exit="exit" />
) : null}
</AnimatePresence>
<div>
<button onClick={toggleClick}>click me</button>
</div>
</Wrapper>
);
}
onExitComplete - exit가 끝나고 실행되는 것
initial - 초기에 애니메이션을 적용할 건지 ( true로 하면 로드될 때 애니메이션이 동작한다. )
<AnimatePresence onExitComplete={toggleLeaving} initial={false}>
animatePresence 로 Slider구현
function App() {
const [currentPage, setCurrentPage] = useState(1);
const clickPrev = () => {
setCurrentPage((current) => (current === 1 ? 1 : current - 1));
};
const clickNext = () => {
setCurrentPage((current) => (current === 10 ? 10 : current + 1));
};
const BoxVars = {
initial: { x: 500, scale: 0, opacity: 0 },
visible: { x: 0, opacity: 1, scale: 1 },
exit: { x: -500, scale: 0 },
};
return (
<Wrapper>
<AnimatePresence>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((num) =>
currentPage === num ? (
<Box variants={BoxVars} initial="initial" animate="visible" exit="exit" key={num}>
{num}
</Box>
) : null
)}
</AnimatePresence>
<div style={{ position: "fixed", bottom: 200 }}>
<button onClick={clickPrev}>prev</button>
<button onClick={clickNext}>next</button>
</div>
</Wrapper>
);
}
custom
variants에 데이터를 보낼수있게 해주는 property
// variants에서 매개변수로 받아서 분기처리 해줄수있다.
const BoxVars = {
initial: (direction: boolean) => ({ x: direction ? -500 : 500, scale: 0, opacity: 0 }),
visible: { x: 0, opacity: 1, scale: 1, transition: { duration: 1 } },
exit: (direction: boolean) => ({
x: direction ? 500 : -500,
scale: 0,
transition: { duration: 0.5 },
}),
};
// AnimatePresence와 해당컴포넌트에 custom props를 넣어준다.
<AnimatePresence custom={direction}>
<Box
custom={direction}
variants={BoxVars}
initial="initial"
animate="visible"
exit="exit"
key={currentPage}
>
{currentPage}
</Box>
</AnimatePresence>
next 버튼을 누르면 1카드가 exit 되는 animation과 2카드 나타나는 animation이 동시에 일어난다.
mode="wait" 를 사용하면 1카드가 exit된 다음 2카드 나타나는 애니메이션이 일어난다.
<AnimatePresence mode="wait" >
</AnimatePresence>
layout
부모요소가 조건에 따라 속성이 변경되서 애니메이션 되어야할때 layout 속성을 준다.
framer motion은 무언가 외부의 힘에 의해 바뀐것을 감지한다.
<Box
style={{
alignItems: isClick ? "center" : "flex-start",
justifyContent: isClick ? "center" : "flex-start",
}}
>
<Circle layout />
</Box>
layoutId
다른 컴포넌트지만 framer motion에게 같은 컴포넌트라고 지정해서 애니메이션을 줄 수 있다.
<Box>{isClick ? <Circle layoutId="circle1" /> : null}</Box>
<Box>{!isClick ? <Circle layoutId="circle1" /> : null}</Box>
useAnimation
다른역활을 하는 함수안에서 애니메이션을 제어하고 싶은경우 사용한다.
(ex 토글하는 클릭 이벤트에서 애니메이션을 제어하는 경우)
inputAnimation = useAnimation();
// 클릭했을때 state active시키는 코드에서 애니메이션을 실행하고 있다.
const toggleSearchActive = () => {
if (searchActive) {
inputAnimation.start({
scaleX: 0,
});
} else {
inputAnimation.start({
scaleX: 1,
});
}
setSearchActive((prev) => !prev);
};
return (
<Input animate={inputAnimation}/>
)
}
useAnimation을 사용할 때에도 variants를 사용할 수 있다.
const navVariants = {
top: {
backgroundColor: "rgba(0, 0, 0, 1)",
},
scroll: {
backgroundColor: "rgba(0, 0, 0, 0)",
},
};
function Header() {
const navAnimation = useAnimation();
const { scrollY } = useScroll();
useEffect(() => {
scrollY.onChange(() => {
if (scrollY.get() > 80) {
navAnimation.start("scroll"); // variants의 property이름
} else {
navAnimation.start("top");
}
});
}, [scrollY, navAnimation]);
return (
<Nav variants={navVariants} animate={navAnimation}>
)
}
간단한 animation 변경은 useAnimation hook을 이용하는것보다
animate props를 수정하는게 더 효율적이다.
animate keyframe
const logoVariants = {
normal: {
fillOpacity: 1,
},
active: {
fillOpacity: [0, 1, 0], // 0, 50%, 100%
transition: {
repeat: Infinity,
},
},
};