🧩 MatchPriorityModal 구현기 – 사용자 친화적인 모달 만들기

2025. 3. 25. 19:30프론트엔드/REACT

728x90

🎯 구현 목적

‘중요한 옵션 선택’은 이번 버전의 핵심 유료 기능이며, 사용자에게 제공하는 가치의 중심입니다.

사실 기능의 핵심 원리 자체는 단순합니다. 매칭에 있어 가장 중요한 우선순위 한 가지만 AI에게 전달하면 됩니다.
정말 간단하게 만들자면, 버튼 4개 중 하나를 클릭해서 고르는 구조도 가능합니다.
모달도, 슬라이드도, 인터랙션도 없이 말이죠.

하지만 저는 그렇게 하지 않았습니다.

"단순한 기능일수록 더 강한 경험을 줘야 한다."

그 이유는, 이것이 유료 옵션이기 때문입니다.
사용자는 그만큼 더 몰입감 있고 가치 있는 체험을 받아야 한다고 생각했습니다.

초기에는 ‘우선순위 선택’이라는 이름으로 슬라이드형 인터랙션을 고집했고,
이후 ‘중요한 옵션 선택’으로 이름은 바뀌었지만, 슬라이드 효과는 그대로 남겨두었습니다.


🎮 사용자가 느끼는 경험을 위해 의도한 UX 요소들

  • 드래그 앤 드롭을 통한 적극적인 선택 과정
  • 선택된 항목이 리스트에서 자연스럽게 사라지고, 다른 항목이 부드럽게 올라오는 애니메이션
  • 말풍선 등장: 상단 텍스트와 하단 텍스트가 순차적으로 등장하는 Fade In 효과
  • 선택 취소 시: 리스트에 빈 공간을 만들고 옵션이 자연스럽게 다시 나타나는 전환
  • 전반적으로 부드럽고 매끄러운 인터랙션을 통해, 단순히 “기능을 수행하는 것”이 아니라
    → **기억에 남는 사용 경험(UX)**으로 남도록 설계했습니다.

✨ 주요 기능

1️⃣ 드래그 앤 드롭 (모바일 터치 기반)

옵션을 길게 누르면 touchstart → touchmove → touchend 이벤트가 동작하고,
드롭 존에 올려놓으면 해당 옵션이 우선순위 항목으로 등록됩니다.

const handleTouchStart = (item, e) => {
  setTouchingItem(item);
  setTouchPos({
    x: e.touches[0].clientX,
    y: e.touches[0].clientY,
  });
  setIsDragging(true);
};

2️⃣ 선택 항목 애니메이션 효과

선택된 옵션은 상단에 강조되며 fade-in 애니메이션으로 등장하고,
"선택 취소" 버튼을 누르면 부드럽게 사라지도록 fade-out 처리됩니다.

const handleCancelSelection = () => {
  setFadeOut(true);
  setTimeout(() => {
    setSelectedItem(null);
    setFadeOut(false);
  }, 500); // 애니메이션 시간
};

CSS 예시:

@keyframes fadeOutTop {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-50px); }
}
.fade-out-top {
  animation: fadeOutTop 0.5s forwards;
}

3️⃣ 선택된 옵션에 따른 문장 생성

Recoil에 저장된 선택값을 기반으로, 사용자가 이해하기 쉬운 문장을 자동 생성합니다.

const priorityMessages = {
  mbti: (value) => `${value}인 사람이면 좋겠어!`,
  hobby: (value) => `${value}에 관심이 많은 사람이면 좋겠어!`,
};

결과 예시:

감정형인 사람이면 좋겠어!
산책에 관심이 많은 사람이면 좋겠어!


4️⃣ 스크롤 가능한 콘텐츠 영역

옵션이 많아졌을 때 모달 내부에만 스크롤이 적용되도록 처리했습니다.
하단 버튼은 항상 고정되도록 만들기 위해, .match-modal-body에 overflow-y: auto를 주고
padding-bottom을 넉넉히 확보했습니다.

.match-modal-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 90px;
}

🧪 구현 중 겪었던 문제와 해결법

문제 상황 원인 해결 방법

Invalid hook call 오류 useState를 컴포넌트 바깥에 선언 함수 컴포넌트 내부로 옮김
선택 취소 시 갑작스러운 사라짐 애니메이션 없이 상태만 초기화 fadeOut 상태 추가 후 setTimeout으로 자연스럽게 제거
모바일 터치 스크롤이 부자연스러움 inertia 스크롤 미적용 -webkit-overflow-scrolling: touch 적용
하단 버튼 가려짐 내부 콘텐츠가 버튼 아래까지 확장 padding-bottom으로 해결

🎨 사용자 경험을 위한 디테일

  • 말풍선 애니메이션("AI가 이걸 제일 중요하게 생각할게요!")로 피드백 제공
  • 버튼의 enabled / disabled 상태 구분을 시각적으로 명확하게 표현
  • 선택 취소 시에도 자연스럽게 사라지는 부드러운 전환 처리

 

728x90