모션 하나 넣는 데 반나절 — 인터랙션 구현 기록
Article
블로그에 스크롤 진입 애니메이션을 넣었다. 라이브러리는 motion(구 Framer Motion). 계획은 한 시간이었는데 반나절이 걸렸다. 코드가 어려워서가 아니다. 느낌이 안 나서였다.
기본 구조는 금방 나온다
variants를 정의하고 whileInView를 걸면 스크롤 진입 애니메이션의 뼈대는 끝난다.
const fadeUp = {
hidden: { opacity: 0, y: 32 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8, ease: [0.16, 1, 0.3, 1] },
},
};
<motion.section
variants={fadeUp}
initial="hidden"
whileInView="visible"
viewport={{ once: true, amount: 0.3 }}
>
여기까지 30분. 문제는 그다음이었다.
이징 곡선 하나로 전부 달라진다
처음엔 ease: "easeOut"을 썼다. 움직이긴 하는데 어딘가 밋밋했다. 요소가 그냥 "이동"할 뿐, 살아있다는 느낌이 없었다.
바꾼 건 이것 하나다.
ease: [0.16, 1, 0.3, 1]
초반에 빠르게 치고 나오고 끝에서 길게 감속하는 곡선이다. 이 곡선을 쓰면 duration을 0.8초까지 늘려도 답답하지 않다. 감속 구간이 길어서 오히려 여유 있어 보인다. 숫자 네 개 바꿨을 뿐인데 사이트의 인상이 달라졌다. 모션의 완성도는 duration이 아니라 이징이 결정한다는 걸 몸으로 배웠다.
리스트에는 stagger를 얹었다.
const staggerContainer = {
visible: {
transition: { staggerChildren: 0.1 },
},
};
간격을 0.08로 하면 급하고 0.15면 늘어진다. 0.1 근처에서 한참을 왔다 갔다 했다.
디자이너의 병이 도졌다
반나절이 걸린 이유가 여기 있다. y 오프셋이 24냐 32냐. viewport amount가 0.2냐 0.3이냐. 새로고침하고, 스크롤하고, 숫자 바꾸고, 다시 새로고침. 이걸 수십 번 반복했다.
디자인 하던 시절에 자간 0.5px 차이로 시안을 다시 뽑던 버릇이 코드에서도 그대로 나온다. 근데 이게 낭비라고 생각하지 않는다. 모션은 사용자가 언어로 설명 못 해도 몸으로 느끼는 영역이다. "뭔가 좋다"와 "뭔가 어색하다"의 차이가 결국 이 숫자들에서 나온다.
페이지 전환에서 배운 것
페이지 전환은 exit 애니메이션이 관건인데, Next.js App Router에서는 나가는 페이지를 붙잡아두는 게 아직 매끄럽지 않다. 그래서 접근을 바꿨다. 나가는 페이지를 애니메이션하는 대신, 화면을 덮는 마스크 레이어를 슬라이드시키고 그 뒤에서 라우팅을 처리하는 방식이다. 마스크가 화면을 덮은 순간 페이지가 바뀌기 때문에 exit를 흉내 낼 필요가 없다.
정리하면 이렇다. 모션 구현의 2할이 코드, 8할이 타이밍 조율이다. 그리고 그 조율은 문서로 배울 수 없고 눈으로만 할 수 있다. 반나절이 아깝지 않았다.