3D처럼 보이지 않았던 틸트 카드의 원인: 계산이 아닌 구조 문제
Stranger Things 카드 스타일의 틸트 인터랙션을 구현했습니다.
카드가 마우스를 따라 회전하고, 배경과 전경은 패럴랙스 되고, 글레어와 파티클까지 더해지게 하였습니다.그런데 계산은 맞는데, 움직임이 자연스럽지 않은 문제가 있었습니다.
처음에는 이 문제를 수학 문제라고 생각했지만 정답은 레이어 설계 문제였습니다. 이 과정에서 정리한 기록을 공유해 봅니다.
1. 문제 상황
최근 넷플릭스 시리즈인 “기묘한 이야기”를 보다가 구글에 기묘한 이야기를 검색했는데 영상 속의 재미있는 인터랙션을 보았다.
그래서 나도 기묘한 이야기를 주제로, 재미있는 인터랙션을 구현해보고 싶었다. 생각난 아이디어로는 기묘한 이야기 포스터로, 마우스를 따라 회전하는 카드 인터랙션이었다.
주요 구성 요소는 다음과 같다.
- 카드 전체 회전 (rotateX, rotateY)
- Background / Middle / Front 레이어 패럴랙스 이동
- glare(빛 반사) 효과
- canvas 파티클 애니메이션
하지만 실제 구현했을 때 아래와 같은 어색함을 느꼈다.
- 카드가 기울어질 때 레이어가 따로 노는 느낌
- 깊이가 아니라 평면이 흔들리는 느낌
- 전체 인터랙션이 매끄럽지 못 한 느낌
그래서 마주한 어색함을 해결해보고자 하였다.
2. 내가 처음 한 생각
처음 든 생각은 이것이었다.
- “normalized 계산이 잘못된 걸까?”
- “각도가 너무 크거나 작은가?”
그래서 의심했던 건 전부 계산식이었다.
const normalizedX = (e.clientX - centerX) / (rect.width / 2);
const tiltY = normalizedX * CONFIG.maxTilt;
마치 그래픽 문제를 풀듯이 수식만 계속 바꿨다. 하지만 숫자를 바꿔도 부자연스러움은 사라지지 않았다.
3. 실제로 해 본 선택
관점을 바꿔서 이렇게 질문하기 시작했다.
“이 카드가 진짜 3D 공간에 존재하고 있을까?”
CSS 구조를 다시 봤다.
.scene {
perspective: 1000px;
}
.card {
transform-style: preserve-3d;
}
겉보기에는 3D처럼 보이지만, 실제 레이어 구조는 이랬다.
- card 전체가 회전
- 내부 레이어들은 같은 평면에서 translate만 이동
- z축(depth)은 존재하지 않음
즉, 나는 3D를 만들고 있다고 생각했지만 실제로는 2D 요소들을 동시에 흔들고 있었을 뿐이었다.
문제 원인을 정리해보면 레이어 역할의 부재이다.
.card__layer--back {
z-index: 1;
}
.card__layer--front {
z-index: 2;
}
z-index는 단지 겹침 순서일 뿐, 공간적 깊이가 아니다. 그래서 회전해도 입체감이 생기지 않았다.
.scene {
perspective: 1000px;
}
.card {
transform-style: preserve-3d;
}
.card__layer--back {
transform: translateZ(-20px);
}
.card__layer--front {
transform: translateZ(20px);
}
따라서 translateZ를 통해 입체감을 주었다. translateZ를 적용하자, 카드가 마치 진짜 공간에 떠 있는 물체처럼 보이기 시작했다. 더 이상 평면이 흔들리는 느낌이 아니라, 깊이를 가진 오브젝트가 회전하는 느낌이었다.
4. 결과
입체감 있는 3D 인터랙션을 구현하려면 아래 내용을 고려해야 한다.
- 환경(부모)에 perspective를 설정
- 컨테이너에 transform-style: preserve-3d를 설정
- 내부 레이어에 translateZ를 설정하여 입체감 부여
5. 지금 다시 해본다면?
수학보다 먼저 구조를 그리고, 레이어 관계를 설계하는 것이 중요하다는 걸 배웠다. 다음엔 처음부터 박스와 깊이 구조를 스케치한 뒤 구현할 것이다.
처음부터 바로 포스터 이미지를 넣고 구현하려고 하니, 복잡해서 쉽게 구현하기 어려웠다. 따라서 간단하게 codepen으로 기초만 먼저 구현을 하고, 원리를 이해하자 쉽게 구현할 수 있었다.
만약 이렇게 새로 도전하는 인터랙션을 구현한다면, 최소 구현 기능을 간단하게 먼저 구현해본 후 시작해보는 것이 더 효과적일 거 같다.