대부분의 인터랙션은 CSS만으로도 충분하다
웹에서 인터랙션을 부드럽게 만들기 위해 애니메이션은 필수다.
CSS만으로도 꽤 많은 걸 할 수 있는데, 예를 들면 이런 것들:
- 버튼에 hover 시 살짝 커지는 효과 (transform: scale)
- 텍스트가 천천히 나타나는 효과 (opacity, transition)
- 슬라이드 메뉴처럼 왼쪽에서 오른쪽으로 들어오는 효과 (transform: translateX)
- 요소가 점점 커지거나 작아지는 스케일 애니메이션
이처럼 transform, opacity, color 같은 layout에 영향을 주지 않는 속성들은 CSS 애니메이션으로도 충분히 구현 가능하고, 성능도 문제가 없다.
layout 을 재계산해야 하는 속성들도 CSS 애니메이션으로 줄 수 있다.
예를 들어:
- width, height가 커지거나 작아지는 애니메이션
- margin, padding 변화
- top, left 위치 변화
마우스를 박스 위에 올려보세요!
이런 속성들은 브라우저가 layout을 다시 계산해야 하기 때문에 비용이 많이 들기는 하지만, CSS 애니메이션으로 구현은 가능하다.
하지만 CSS만으로는 불가능한 변화도 있다
하지만 문제는 CSS로는 아예 애니메이션을 줄 수 없는 경우들!
대표적으로 이런 상황이다:
- justify-content: flex-start → center처럼 부모의 정렬 속성이 바뀌는 경우
- display: none → flex처럼 요소의 존재 자체가 바뀌는 경우
- 리스트 아이템이 추가되거나 빠지면서 다른 요소들이 재배치되는 경우
- 컴포넌트가 완전히 바뀌는데도 위치를 유지하면서 자연스럽게 바뀌는 효과를 주고 싶을 때
See the Pen Untitled by NAJEONG KIM (@Najeong-Kim-the-flexboxer) on CodePen.
이런 건 중간 상태라는 게 존재하지 않기 때문에 CSS에서 transition이나 keyframes로 애니메이션을 줄 수가 없다.
그래서 등장하는 게 transform 기반의 간접 애니메이션이고, 그걸 가능하게 해주는 방식이 FLIP 애니메이션이다.
FLIP 방식이란?
이런 이유로 많이 쓰는 방식이 FLIP(First, Last, Invert, Play) 이라는 건데, 이걸 쓰면 layout에 변화가 있더라도 transform만으로 애니메이션을 줄 수 있다!
간단히 설명하면 이런 흐름이다:
- First: 변경 전 요소의 위치와 크기 측정
- Last: 변경 후 요소의 위치와 크기 측정
- Invert: 최종 위치에서 초기 위치로 transform을 줘서 순간적으로 되돌린 다음
- Play: 그걸 다시 최종 위치로 애니메이션으로 보내는 방식
See the Pen Untitled by NAJEONG KIM (@Najeong-Kim-the-flexboxer) on CodePen.
이걸 잘 응용하면, 원래는 애니메이션을 줄 수 없는 변화에도 자연스럽게 애니메이션을 적용할 수 있다.
그리고 대표적으로 Framer Motion이 FLIP 방식을 잘 활용하고 있다 😁
📢 위치와 크기 애니메이션을 함께 줄 때 주의할 점
위치랑 크기를 동시에 transform으로 바꾸다 보면 transform-origin에 따라서 애니메이션이 이상하게 보일 수 있다.
이럴 땐 중심점을 기준으로 거리 차이를 계산해서 적용하면 비교적 자연스럽게 보인다.
부모 요소가 커지는데 자식은 그대로 있게 하고 싶을 때?
레이아웃이 바뀔 때 부모가 커지면 당연히 자식도 영향을 받는다.
근데 자식은 그냥 그대로 있고 싶다면, 자식 요소에 상쇄하는 transform을 적용해서 부모 변화에 따라가지 않게 할 수 있다.
다만, 이 방법은 애니메이션의 타이밍이나 easing 곡선이 다르면 어색하게 보일 수 있다.
이 문제를 framer-motion에서는 projection node로 해결했다고 한다. (이건 나중에 더 공부해봐야겠다!)
Framer Motion에서 layout 애니메이션 주기
Framer Motion은 layout 속성 하나만 써도 요소의 위치나 크기 변화에 자동으로 애니메이션을 준다.
See the Pen framer-motion-example by NAJEONG KIM (@Najeong-Kim-the-flexboxer) on CodePen.
Shared layout animations을 적용하려면, layoutId를 동일하게 설정하면 된다.
{isOpen ? (
<motion.div layoutId="card" className="expanded" />
) : (
<motion.div layoutId="card" className="collapsed" />
)}
두 컴포넌트에 같은 layoutId를 주면, 기존 컴포넌트에서 새 컴포넌트로 변경될 때 부드럽게 전환된다.
AnimatePresence로 퇴장 애니메이션 주기
리스트 같은 거에서 요소가 사라질 때 퇴장 애니메이션을 주고 싶다면 AnimatePresence를 쓰면 된다.
기본적으로는 입장/퇴장 모두 애니메이션이 적용되는데, 처음 등장할 땐 애니메이션 없이 바로 적용되게 하고 싶다면 initial={false} 옵션을 쓰면 된다.
<AnimatePresence initial={false}>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
)}
</AnimatePresence>
또 한 가지 중요한 점!
React에서 내부 상태 변화로 인해 애니메이션을 주려면 key를 꼭 바꿔줘야 한다.
key가 안 바뀌면 React 입장에선 같은 요소로 보기 때문에 애니메이션이 안 먹는다.
popLayout, wait ?
퇴장 타이밍 관련해서는 popLayout이나 wait 같은 mode 옵션을 써주면, 애니메이션이 끝나기 전/후에 요소가 사라지도록 제어할 수 있다.
이 옵션을 몰라서 직접 타이밍 계산해서 코드를 작성했는데!
뭔가 애니메이션에 대해서 더 잘 알게된 느낌이다.
참고한 자료
'코딩 > 공부' 카테고리의 다른 글
| 10 Years of Let’Swift 행사 참가 후기 (0) | 2026.04.02 |
|---|---|
| Frontend Fundamentals 모의고사 2회차 후기 (0) | 2026.03.29 |
| GreatFrontEnd 풀이 - Clamp (0) | 2026.01.11 |
| 순차 애니메이션 만들기: Framer Motion stagger 사용법과 예제 (4) | 2025.08.18 |
