랜딩 페이지에서 먼저 시선을 붙드는 요소로 전환되는 텍스트 애니메이션만 한 게 없습니다. 단순한 fade in/out이 아니라 글자 하나하나가 순서대로 솟아오르며 문구가 바뀌는 효과 말이죠. Framer Motion으로 구현한 과정을 설계 의도부터 코드까지 적어둡니다.
1. 완성된 모습
code
Building [accessible UIs] with care.
Building [scalable systems] with care.
Building [beautiful interfaces] with care.
대괄호 안의 문구가 2초마다 자동으로 바뀌고, 그때마다 각 글자가 아래에서 위로 솟아오르는 spring 애니메이션이 붙습니다.
2. 설계 방향
코드를 짜기 전에 요구사항부터 못 박았습니다. 글자(character) 단위로 쪼개 stagger 애니메이션을 걸 것, 자동 로테이션과 수동 제어(next/previous/jumpTo)를 모두 지원할 것, forwardRef로 외부에서 애니메이션을 제어할 수 있게 할 것, splitBy 옵션으로 글자·단어·줄 단위를 고를 수 있게 할 것, 그리고 TypeScript로 타입 안전성을 확보할 것.
요구사항을 먼저 정리해두니 컴포넌트가 가져야 할 prop과 ref API의 윤곽이 잡혔습니다. 재사용 가능한 컴포넌트를 목표로 둔 순간 단순한 데모와는 설계가 달라졌습니다.
3. 핵심 구현
3-1. 텍스트를 글자 단위로 분리
가장 먼저 풀어야 할 문제는 글자를 어떻게 정확히 쪼개느냐였습니다. 단순히 문자열을 쪼개면 이모지나 한글 같은 다중 바이트 문자가 깨지기 때문에, Intl.Segmenter를 우선 쓰고 지원하지 않는 환경에서는 Array.from으로 폴백했습니다.
staggerFrom 옵션으로 애니메이션이 퍼져나가는 기점을 바꿀 수 있습니다. first는 첫 글자부터 순서대로, last는 마지막 글자부터 역순으로, center는 가운데에서 바깥으로, random은 무작위 순서로 등장합니다. 같은 문구라도 기점 하나로 인상이 꽤 달라져서 이것저것 바꿔보는 재미가 있었습니다.
AnimatePresence가 key 변화를 감지해 이전 문구는 위로 빠져나가고 새 문구는 아래에서 올라오게 만듭니다. 여기서 각 글자를 감싼 overflow: hidden 래퍼가 중요합니다. 애니메이션 도중 글자가 영역 밖으로 삐져나와 보이는 걸 잘라주는데, 이 한 겹이 없으면 글자가 칸 밖에서 떠다니는 것처럼 보입니다.