Framer Motion은 React 애플리케이션에서 애니메이션을 쉽게 추가할 수 있도록 도와주는 JavaScript 라이브러리입니다.
npm install framer-motion
import { motion } from "framer-motion";
무언가를 애니메이트 할 때 motion으로 HTML element를 불려와야 합니다.
<motion.div />
styled-components
를 사용하여 motion component
를 만들 수 있습니다.
const Box = styled(motion.div)``;
부분의 애니메이션은 motion 구성 요소와 animate prop으로 수행됩니다.
<Box animate={{ borderRadius: "100px" }} />
transition
prop에 기본 전환을 전달하여 다양한 종류의 애니메이션을 정의할 수 있습니다.
이 속성을 사용하면 요소가 변화할 때 애니메이션의 duration
, easing
, delay
, repeat
등을 설정할 수 있습니다.
이러한 prop을 활용하여 요소들 사이의 부드럽고 자연스러운 애니메이션 효과를 만들어낼 수 있습니다.
3초 후 애니메이션이 동작합니다.
<Box transition={{ delay: 3 }} animate={{ borderRadius: "100px" }} />
완료 될때까지 3초가 소요됩니다.
<Box transition={{ duration: 3 }} animate={{ borderRadius: "100px" }} />
spring
은 애니메이션의 효과를 부드럽게 만들어주는 속성 중 하나입니다.
type
의 기본값이며 요소의 움직임이 탄력적이고 자연스럽게 보이도록 설정할 수 있습니다.
spring transition
에서 사용되는 일부 주요한 속성있습니다.
Stiffness (강도)
: 스프링의 강도를 나타냅니다. 값이 높을수록 스프링의 힘이 강해져 빠르게 움직입니다. 이 값이 높을수록 더 빠르게 변화하게 됩니다.
Damping (감쇠)
: 스프링의 진동을 억제하는 비율을 나타냅니다. 값이 낮을수록 스프링의 진동이 더 오래 지속됩니다. 따라서 낮은 값일수록 더 많은 반동(bounce)이 생기게 됩니다.
Mass (질량)
: 스프링의 질량을 나타냅니다. 질량이 클수록 느리게 움직이게 되어 더 많은 반동이 발생할 수 있습니다.
Bounce (반동)
: 스프링 애니메이션에서 요소가 끝점에 도달했을 때의 반동 효과를 의미합니다. 이 값이 크면 끝에 도달했을 때의 반동이 더 강력하게 작용하며, 애니메이션이 끝나고 나서 추가적인 반동이 발생합니다.
<Box
transition={{ type: "spring", bounce: 0.1 }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
tween
은 시작 지점에서 끝 지점까지의 값을 부드럽게 보간하여 애니메이션을 만들어내는 방식입니다.
이를 통해 요소가 부드럽게 변화하고, 시작과 끝 사이를 자연스럽게 이동합니다.
<Box
transition={{ type: "tween" }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
initial
은 요소의 초기 상태를 설정하는 데 사용됩니다.
이 속성을 사용하면 요소가 렌더링되었을 때 초기에 적용되는 애니메이션 또는 스타일을 지정할 수 있습니다.
<Box initial={{ scale: 0 }} animate={{ scale: 1 }} />
여러 애니메이션 상태를 하나의 객체로 정의하여 재사용할 수 있도록 도와주는 기능입니다.
variants
사용하지 않음
function App() {
return (
<Wrapper>
<Box
transition={{ type: "spring", bounce: 0.1 }}
initial={{ scale: 0 }}
animate={{ scale: 1, rotateZ: 360 }}
/>
</Wrapper>
);
}
variants
사용
const myVars = {
start: { scale: 0 },
end: { scale: 1, rotateZ: 360, transition: { type: "spring", bounce: 0.1 } },
};
function App() {
return (
<Wrapper>
<Box variants={myVars} initial="start" animate="end" />
</Wrapper>
);
}
Propagation
은 애니메이션 상태의 상속 및 덮어쓰기를 조절하는 기능을 의미합니다.
variants
객체 내의 하위 요소에 대한 설정을 상속하거나 덮어쓰는 방식으로 작동합니다.
상위 요소에서 정의한 애니메이션 상태를 하위 요소에서 상속받아 사용하거나, 필요한 경우 하위 요소에서 해당 상태를 재정의하여 덮어쓸 수 있습니다.
Circle
컴포넌트의 initial
과 animate
Enter animations을 사용하려고 합니다.
function App() {
return (
<Wrapper>
<Box variants={boxVariants} 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>
);
}
Propagation
으로 인해 Circle
은 Box
와 같이 initial="start" animate="end"
을 생략할 수 있습니다.
Circle
은 circleVariants
의 start
와 end
를 각각 initial
과 animate
에 적용합니다.
function App() {
return (
<Wrapper>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
<Circle variants={circleVariants}/>
</Box>
</Wrapper>
);
}
Orchestration
은 여러 개의 애니메이션 속성들이 함께 동작할 때 그들 사이의 조정을 의미합니다.
이를 사용하여 여러 애니메이션 간의 타이밍, 순서, 그리고 동작 방식을 조절할 수 있습니다.
일반적으로 orchestration은 두 가지 값으로 정의됩니다:
Synchronize (동기화)
: 애니메이션들이 동시에 시작하고 종료되도록 설정됩니다. 이 경우 애니메이션들이 함께 실행되며, 동일한 지속 시간과 시작 시간을 갖습니다.
Sequence (순차)
: 애니메이션들이 순차적으로 발생하도록 설정됩니다. 이 경우 각 애니메이션이 이전 애니메이션이 종료된 후에 시작됩니다.
Box
와 Circle
이 동시에 나타납니다.
const boxVariants = {
start: {
opacity: 0,
scale: 0.5,
},
end: {
scale: 1,
opacity: 1,
transition: {
type: "spring",
duration: 0.5,
bounce: 0.5,
},
},
};
const circleVariants = {
start: {
opacity: 0,
y: 10,
},
end: {
opacity: 1,
y: 0,
},
};
function App() {
return (
<Wrapper>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
</Box>
</Wrapper>
);
}
delayChildren
을 사용하여 Box
의 자식 요소를 0.5초 이후에 나타냅니다.
const boxVariants = {
start: {
opacity: 0,
scale: 0.5,
},
end: {
scale: 1,
opacity: 1,
transition: {
type: "spring",
duration: 0.5,
bounce: 0.5,
delayChildren: 0.5,
},
},
};
const circleVariants = {
start: {
opacity: 0,
y: 10,
},
end: {
opacity: 1,
y: 0,
},
};
function App() {
return (
<Wrapper>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
</Box>
</Wrapper>
);
}
staggerChildren
을 사용하여 Box
의 자식 요소를 순서대로 각각 0.2초 차이를 두고 생성합니다.
const boxVariants = {
start: {
opacity: 0,
scale: 0.5,
},
end: {
scale: 1,
opacity: 1,
transition: {
type: "spring",
duration: 0.5,
bounce: 0.5,
delayChildren: 0.5,
staggerChildren: 0.2,
},
},
};
const circleVariants = {
start: {
opacity: 0,
y: 10,
},
end: {
opacity: 1,
y: 0,
},
};
function App() {
return (
<Wrapper>
<Box variants={boxVariants} initial="start" animate="end">
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
<Circle variants={circleVariants} />
</Box>
</Wrapper>
);
}
요소를 Drag하여 이동시킬 때 애니메이션을 적용합니다.
사용자가 요소를 Drag할 때 해당 요소를 부드럽게 이동시키는 효과를 줄 수 있습니다.
<motion.div drag />
드래그 애니메이션을 조절하는 속성 중 하나입니다.
이를 통해 요소가 드래그를 끝낼 때 원래 위치로 자연스럽게 스냅되도록 설정할 수 있습니다.
드래그 애니메이션을 제어하는 속성 중 하나입니다.
이를 통해 드래그 중에 요소에 탄성을 부여하여 드래그 동작의 물리적 특성을 조절할 수 있습니다.
0에서 1이 될수록 탄성이 적어집니다.
드래그하는 요소의 움직임을 제한하는 데 사용되는 속성입니다.
이를 사용하여 요소가 드래그되는 영역을 제한하거나 특정한 방향으로만 드래그될 수 있도록 설정할 수 있습니다.
<motion.div drag dragConstraints={{top: -50, bottom: 50 left: -50, right: 50}} />
<motion.div drag="x" />
const boxVariants = {
hover: { rotateZ: 90 },
click: { borderRadius: "100px" },
};
function App() {
const biggerBoxRef = useRef(null);
return (
<Wrapper>
<BiggerBox ref={biggerBoxRef}>
<Box
drag
dragSnapToOrigin
dragElastic={0.5}
dragConstraints={biggerBoxRef}
variants={boxVariants}
whileHover="hover"
whileTap="click"
/>
</BiggerBox>
</Wrapper>
);
}
Drag와 유사하지만 좀 더 자유로운 움직임을 허용하며, Drag가 아니라 요소를 사용자의 Pan 제스처에 맞춰 움직입니다.
사용자가 화면을 Swipe할 때 요소에 애니메이션을 적용합니다.
예를 들어, Swipe 동작으로 요소를 서서히 사라지게 하거나 다른 방향으로 이동시키는 등의 효과를 줄 수 있습니다.
사용자가 요소를 Tap할 때 애니메이션을 발생시킵니다.
Tap 동작에 대한 반응으로 요소를 확대, 축소, 색상 변경 등의 변화를 줄 수 있습니다.
whileHover
, whileTap
, whileFocus
, whileDrag
, whileInView
와 같은 속성들은 제스처(Hover
, Tap
, Focus
, Drag
, View
내에 있을 때 등) 동안 특정한 애니메이션을 정의하는 데 사용됩니다.
whileHover
: 요소가 호버(마우스가 요소 위에 올라갔을 때) 상태일 때 애니메이션을 적용합니다.
주로 호버 상태일 때 요소의 크기를 변경하거나 색상을 변화시키는 등의 효과를 줄 때 사용됩니다.
whileTap
: 요소가 탭(클릭) 상태일 때 애니메이션을 적용합니다.
주로 요소가 탭되었을 때 크기를 축소하거나 이동시키는 등의 효과를 줄 때 활용됩니다.
whileFocus
: 요소가 포커스를 받았을 때 애니메이션을 적용합니다. 특히 입력 필드와 같은 요소가 포커스를 받았을 때 테두리 색상 변경이나 크기 변화 등의 효과를 줄 때 사용됩니다.
whileDrag
: 요소가 드래그 상태일 때 애니메이션을 적용합니다. 요소가 드래그되는 동안 위치를 변경하거나 투명도를 조절하는 등의 효과를 줄 때 사용됩니다.
whileInView
: 요소가 화면 안에 보일 때의 상태를 나타내며, 화면에 들어왔을 때 또는 나갔을 때의 애니메이션을 정의할 수 있습니다.
const boxVariants = {
hover: { scale: 1.5, rotateZ: 90 },
click: { scale: 1, borderRadius: "100px" },
drag: { backgroundColor: "rgb(46, 204, 113)", transition: { duration: 1 } },
};
function App() {
return (
<Wrapper>
<Box
drag
variants={boxVariants}
whileHover="hover"
whileDrag="drag"
whileTap="click"
/>
</Wrapper>
);
}
useMotionValue
훅은 React 함수 컴포넌트 내에서 사용되며, 단일한 애니메이션 값을 생성하고 추적하는 데 사용됩니다.
import { motion, useMotionValue } from "framer-motion";
function App() {
const x = useMotionValue(0);
return (
<Wrapper>
<button onClick={() => x.set(200)}>click me</button>
<Box style={{ x }} />
</Wrapper>
);
}
useMotionValueEvent
훅은 애니메이션 값이 변경될 때 트리거할 수 있는 이벤트를 생성합니다.
import { motion, useMotionValue, useMotionValueEvent } from "framer-motion";
function App() {
const x = useMotionValue(0);
useMotionValueEvent(x, "change", (l) => {
console.log(l); // 200
});
return (
<Wrapper>
<button onClick={() => x.set(200)}>click me</button>
<Box style={{ x }} />
</Wrapper>
);
}
한 범위의 모션 값(애니메이션 값)을 다른 범위의 값으로 매핑하거나 변환하는 데 사용됩니다.
import { motion, useMotionValue, useTransform } from "framer-motion";
const x = useMotionValue(1)
const y = useMotionValue(1)
const z = useTransform(() => x.get() + y.get()) // 2
useTransform(모션 값, 입력 범위, 출력 범위)
Box
의 x좌표가 -800일 때 크기는 2배가 되고, 0일 때 1배가 되고, 800일 때 0.1배가 됩니다.
function App() {
const x = useMotionValue(0);
const scale = useTransform(x, [-800, 0, 800], [2, 1, 0.1]);
return (
<Wrapper>
<Box style={{ x, scale }} drag="x" dragSnapToOrigin />
</Wrapper>
);
}
useScroll
훅은 스크롤 이벤트를 감지하고 컴포넌트의 상태를 변경하는 데 사용됩니다.
이 훅을 사용하면 페이지의 스크롤 위치에 따라 애니메이션을 트리거하거나 UI를 변경하는 등 다양한 기능을 구현할 수 있습니다.
scrollX / Y
: 픽셀 단위로 정확한 스크롤 위치를 나타내는 값입니다.
수평 방향(scrollX
)과 수직 방향(scrollY
)의 스크롤 위치를 각각 얻을 수 있습니다.
scrollXProgress / YProgress
: 정의된 오프셋(일반적으로 요소의 시작과 끝) 사이의 스크롤 위치를 0부터 1까지의 값으로 표현한 것입니다.
예를 들어, 요소의 시작부터 끝까지의 스크롤 위치를 0에서 1로 변환하여 나타내줍니다.
import { useScroll } from "framer-motion"
const { scrollX, scrollY, scrollXProgress, scrollYProgress } = useScroll()
스크롤로 페이지를 내렸을 때 scrollY
의 결과입니다.
스크롤 위치를 픽셀 단위로 볼 수 있습니다.
const { scrollY } = useScroll()
useMotionValueEvent(scrollY, "change", (latest) => {
console.log("Page scroll: ", latest)
})
스크롤로 페이지를 내렸을 때 scrollYProgress
의 결과입니다.
스크롤을 아래로 내릴수록 1에 가까워집니다.
const { scrollYProgress } = useScroll()
useMotionValueEvent(scrollYProgress, "change", (latest) => {
console.log("Page scroll: ", latest)
})
useTransform
과 함께 사용한 결과입니다.
function App() {
const x = useMotionValue(0);
const { scrollYProgress } = useScroll();
// x좌표에 따라 요소를 2D 평면에서 z축을 중심으로 회전시킵니다
const rotateZ = useTransform(x, [-800, 800], [-360, 360]);
// x좌표에 따라 색깔이 변합니다.
const gradient = useTransform(
x,
[-800, 800],
[
"linear-gradient(135deg, rgb(0, 210, 238), rgb(0, 83, 238))",
"linear-gradient(135deg, rgb(0, 238, 155), rgb(238, 178, 0))",
]
);
// 스크롤이 내려갈 수록 scale은 5배에 가까워집니다.
const scale = useTransform(scrollYProgress, [0, 1], [1, 5]);
return (
<Wrapper style={{ background: gradient }}>
<Box style={{ x, rotateZ, scale }} drag="x" dragSnapToOrigin />
</Wrapper>
);
}
SVG를 가져오려면 아래와 같은 코드를 사용합니다.
<svg
xmlns="http://www.w3.org/2000/svg"
height="16"
width="14"
viewBox="0 0 448 512"
>
<path
d="M224 373.1c-25.2-31.7-40.1-59.4-45-83.2-22.6-88 112.6-88 90.1 0-5.5 24.3-20.3 52-45 83.2zm138.2 73.2c-42.1 18.3-83.7-10.9-119.3-50.5 103.9-130.1 46.1-200-18.9-200-54.9 0-85.2 46.5-73.3 100.5 6.9 29.2 25.2 62.4 54.4 99.5-32.5 36.1-60.6 52.7-85.2 54.9-50 7.4-89.1-41.1-71.3-91.1 15.1-39.2 111.7-231.2 115.9-241.6 15.8-30.1 25.6-57.4 59.4-57.4 32.3 0 43.4 25.9 60.4 59.9 36 70.6 89.4 177.5 114.8 239.1 13.2 33.1-1.4 71.3-37 86.6zm47-136.1C280.3 35.9 273.1 32 224 32c-45.5 0-64.9 31.7-84.7 72.8C33.2 317.1 22.9 347.2 22 349.8-3.2 419.1 48.7 480 111.6 480c21.7 0 60.6-6.1 112.4-62.4 58.7 63.8 101.3 62.4 112.4 62.4 62.9 .1 114.9-60.9 89.6-130.2 0-3.9-16.8-38.9-16.8-39.6z"
></path>
</svg>
SVG도 motion
을 통해 애니메이트 할 수 있습니다.
SVG는 path
를 가지고 있고 path
는 fill
을 가지고 있습니다.
fill
을 통해 색깔을 바꿀 수 있으며 fill: currentColor
은 는 특정 요소의 색상을 현재 적용된 텍스트 또는 요소의 색과 일치하도록 설정하는 CSS 속성입니다.
즉, path
가 color
를 가질 것이라는 의미입니다.
stroke
는 도형의 윤곽선을 말합니다.
pathLength
속성은 요소의 실제 길이를 조정할 수 있습니다.
1은 완성된 길이이며 0이 되면 사라지게 됩니다.
이를 활용하여 윤곽선이 그려지는 듯한 애니메이션을 구현할 수 있습니다.
stroke를 통해 윤곽선을 그린 후 fill을 통해 배경을 채우는 효과를 줄 수 있습니다.
const Svg = styled.svg`
width: 300px;
height: 300px;
path {
stroke: white;
stroke-width: 2;
}
`;
const svg = {
start: { pathLength: 0, fill: "rgba(255, 255, 255, 0)" },
end: {
fill: "rgba(255, 255, 255, 1)",
pathLength: 1,
},
};
function App() {
return (
<Wrapper>
<Svg
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
>
<motion.path
variants={svg}
initial="start"
animate="end"
transition={{
default: { duration: 5 },
fill: { duration: 3, delay: 3 },
}}
d="M224 373.12c-25.24-31.67-40.08-59.43-45-83.18-22.55-88 112.61-88 90.06 0-5.45 24.25-20.29 52-45 83.18zm138.15 73.23c-42.06 18.31-83.67-10.88-119.3-50.47 103.9-130.07 46.11-200-18.85-200-54.92 0-85.16 46.51-73.28 100.5 6.93 29.19 25.23 62.39 54.43 99.5-32.53 36.05-60.55 52.69-85.15 54.92-50 7.43-89.11-41.06-71.3-91.09 15.1-39.16 111.72-231.18 115.87-241.56 15.75-30.07 25.56-57.4 59.38-57.4 32.34 0 43.4 25.94 60.37 59.87 36 70.62 89.35 177.48 114.84 239.09 13.17 33.07-1.37 71.29-37.01 86.64zm47-136.12C280.27 35.93 273.13 32 224 32c-45.52 0-64.87 31.67-84.66 72.79C33.18 317.1 22.89 347.19 22 349.81-3.22 419.14 48.74 480 111.63 480c21.71 0 60.61-6.06 112.37-62.4 58.68 63.78 101.26 62.4 112.37 62.4 62.89.05 114.85-60.86 89.61-130.19.02-3.89-16.82-38.9-16.82-39.58z"
></motion.path>
</Svg>
</Wrapper>
);
}
요소가 마운트(mount)되거나 언마운트(unmount)될 때 애니메이션을 적용하는 데 사용됩니다.
주로 요소의 입장과 퇴장에 따른 애니메이션 효과를 제어할 때 활용됩니다.
AnimatePresence
는 직계 자식요소가 React 트리에서 제거되는 시기를 감지하여 작동합니다.
const boxVariants = {
initial: {
opacity: 0,
scale: 0,
},
visible: {
opacity: 1,
scale: 1,
rotateZ: 360,
},
leaving: {
opacity: 0,
scale: 0,
y: 50,
},
};
function App() {
const [showing, setShowing] = useState(false);
const toggleShowing = () => setShowing((prev) => !prev);
return (
<Wrapper>
<button onClick={toggleShowing}>Click</button>
<AnimatePresence>
// 조건을 사용하여 Box를 마운트 하거나 언마운트
{showing ? (
<Box
variants={boxVariants}
initial="initial"
// Box가 마운트될 때
animate="visible"
// Box가 언마운트될 때
exit="leaving"
/>
) : null}
</AnimatePresence>
</Wrapper>
);
}
AnimatePresence
을 사용하면 carousel도 비교적 간단하게 구현할 수 있습니다.
단일 자식 key
를 변경하면 슬 라이드쇼(슬라이더)와 같은 컴포넌트를 쉽게 만들 수 있습니다.
React에서 key는 고유하기 때문에 만약 component의 key가 바뀌면 새로운 걸 re-render 합니다.
따라서 이전 component는 없어지고 AnimatePresence
가 exit animation을 실행합니다.
import styled from "styled-components";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
const boxVariants = {
invisible: {
x: 500,
opacity: 0,
scale: 0,
},
visible: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
},
},
exit: { x: -500, opacity: 0, scale: 0, transition: { duration: 1 } },
};
function App() {
const [visible, setVisible] = useState(1);
const nextPlease = () => setVisible((prev) => (prev === 10 ? 1 : prev + 1));
const prevPlease = () => setVisible((prev) => (prev === 1 ? 10 : prev - 1));
return (
<Wrapper>
<AnimatePresence>
<Box
variants={boxVariants}
initial="invisible"
animate="visible"
exit="exit"
key={visible}
>
{visible}
</Box>
</AnimatePresence>
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
</Wrapper>
);
}
custom을 사용하려면 variant의 object를 함수로 바꿔야 합니다.
custom
속성으로 isBack
상태를 variants
로 전달하여 isBack
상태에 따라 애니메이션을 변경합니다.
const boxVariants = {
entry: (isBack) => ({
x: isBack ? -500 : 500,
opacity: 0,
scale: 0,
}),
center: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
},
},
exit: (isBack) => ({
x: isBack ? 500 : -500,
opacity: 0,
scale: 0,
transition: { duration: 1 },
}),
};
function App() {
const [visible, setVisible] = useState(1);
const [isBack, setIsBack] = useState(false);
const nextPlease = () => {
setIsBack(false);
setVisible((prev) => (prev === 10 ? 1 : prev + 1));
};
const prevPlease = () => {
setIsBack(true);
setVisible((prev) => (prev === 1 ? 10 : prev - 1));
};
return (
<Wrapper>
<AnimatePresence custom={isBack}>
<Box
custom={isBack}
variants={boxVariants}
initial="entry"
animate="center"
exit="exit"
key={visible}
>
{visible}
</Box>
</AnimatePresence>
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
</Wrapper>
);
}
위의 예시에서는 사라질 Box
의 exit animation와 나타날 Box
의 enter animation이 동시에 실행됩니다.
만약 exit animation 이후 enter animation를 실행하고 싶다면 AnimatePresence
의 mode={wait}
속성을 사용하면 해결됩니다.
const boxVariants = {
entry: (isBack) => ({
x: isBack ? -500 : 500,
opacity: 0,
scale: 0,
}),
center: {
x: 0,
opacity: 1,
scale: 1,
transition: {
duration: 1,
},
},
exit: (isBack) => ({
x: isBack ? 500 : -500,
opacity: 0,
scale: 0,
transition: { duration: 1 },
}),
};
function App() {
const [visible, setVisible] = useState(1);
const [isBack, setIsBack] = useState(false);
const nextPlease = () => {
setIsBack(false);
setVisible((prev) => (prev === 10 ? 1 : prev + 1));
};
const prevPlease = () => {
setIsBack(true);
setVisible((prev) => (prev === 1 ? 10 : prev - 1));
};
return (
<Wrapper>
<AnimatePresence custom={isBack} mode={"wait"}>
<Box
custom={isBack}
variants={boxVariants}
initial="entry"
animate="center"
exit="exit"
key={visible}
>
{visible}
</Box>
</AnimatePresence>
<button onClick={nextPlease}>next</button>
<button onClick={prevPlease}>prev</button>
</Wrapper>
);
}
initial
속성은 초기 상태를 정의하는 데 사용됩니다.
initial={false}
로 설정하면 자식 요소가 처음 렌더링될 때 애니메이션을 사용하지 않습니다.
onExitComplete
프로퍼티는 요소가 애니메이션을 통해 렌더링에서 제거된 후 에 호출되는 콜백 함수를 설정하는 데 사용됩니다.
function Home() {
const [index, setIndex] = useState(0);
const [leaving, setLeaving] = useState(false);
const { data, isLoading } = useQuery<IGetMoviesResult>(
["movies", "nowPlaying"],
getMovies
);
const increaseIndex = () => {
if (leaving) return;
toggleLeaving();
setIndex((prev) => prev + 1);
};
const toggleLeaving = () => setLeaving((prev) => !prev);
return (
<Wrapper>
{isLoading ? (
<Loader>Loading...</Loader>
) : (
<>
<Banner bgPhoto={makeImagePath(data?.results[0].backdrop_path || "")}>
<Title>{data?.results[0].title}</Title>
<Overview>{data?.results[0].overview}</Overview>
</Banner>
<Slider>
// 요소가 `unmount`될 때 toggleLeaving 실행
<AnimatePresence initial={false} onExitComplete={toggleLeaving}>
<Row
variants={rowVariants}
initial="hidden"
animate="visible"
exit="exit"
transition={{ type: "tween", duration: 1 }}
key={index}
>
{[1, 2, 3, 4, 5, 6].map((i) => (
<Box key={i}>{i}</Box>
))}
</Row>
</AnimatePresence>
</Slider>
</>
)}
</Wrapper>
);
레이아웃 변화가 발생할 때 요소가 부드럽게 애니메이션되도록 하는 역할을 합니다.
layout
을 적용하면 리렌더링으로 인해 발생하는 모든 레이아웃 변경이 애니메이션화됩니다.
layout
을 적용하지 않는다면 레이아웃은 위와 같이 변경됩니다.
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Box
style={{
justifyContent: clicked ? "center" : "flex-start",
alignItems: clicked ? "center" : "flex-start",
}}
>
<Circle />
</Box>
</Wrapper>
);
}
Circle
에 layout
을 적용하면 레이아웃 변경이 애니메이션화됩니다.
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Box
style={{
justifyContent: clicked ? "center" : "flex-start",
alignItems: clicked ? "center" : "flex-start",
}}
>
<Circle layout />
</Box>
</Wrapper>
);
}
요소들 간의 레이아웃 전환을 부드럽게 처리하는 데 사용됩니다.
이를 통해 요소의 위치, 크기, 애니메이션 등의 변경 사항이 일어날 때, 두 요소 간의 전환을 부드럽게 처리하여 사용자 경험을 향상시킬 수 있습니다.
layoutId
를 사용하여 요소들 사이에 고유한 식별자를 지정할 수 있습니다.
이 식별자는 요소들 간에 레이아웃이 변할 때(예: 페이지 전환, 리스트 아이템 변경 등) 같은 layoutId
를 가진 요소들이 서로 부드럽게 전환될 수 있도록 해줍니다.
layoutId
를 사용하지 않는다면 레이아웃은 위와 같이 변경됩니다.
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Box>{!clicked ? <Circle style={{ borderRadius: 50 }} /> : null}</Box>
<Box>
{clicked ? <Circle style={{ borderRadius: 0, scale: 2 }} /> : null}
</Box>
</Wrapper>
);
}
layoutId
를 가진 Circle
이 서로 부드럽게 전환될 수 있도록 해줍니다.
function App() {
const [clicked, setClicked] = useState(false);
const toggleClicked = () => setClicked((prev) => !prev);
return (
<Wrapper onClick={toggleClicked}>
<Box>
{!clicked ? (
<Circle layoutId="circle" style={{ borderRadius: 50 }} />
) : null}
</Box>
<Box>
{clicked ? (
<Circle layoutId="circle" style={{ borderRadius: 0, scale: 2 }} />
) : null}
</Box>
</Wrapper>
);
}
출처:
Framer Motion
노마드 코더