안녕하세요, 이제는 피플펀드컴퍼니가 아닌 PFCT 프론트엔드 개발자로 인사드리게 된 김선미입니다. PFCT 홈페이지, 다들 보셨나요?

( 아직 PFCT 홈페이지를 보지 않으셨다면? 지금 확인해보세요! 링크)

리브랜딩 후 보다 확장된 PFCT를 보여드리기 위해, 홈페이지도 그에 맞춰 새롭게 태어난 것인데요. 지금부터 그 좌충우돌 개발 과정을 낱낱이 보여드리겠습니다!

때는 23년도 10월, 크레딧플래닛 서비스 온보딩 개편 작업을 마무리하고 있었습니다. 온보딩에서 신규 신용분석 점수제인 ‘플래닛 스코어’를 설명하고 ‘전환율을 높이기’라는 과제를 두고 애니메이션이 가미된 웹뷰 방식을 채택하게 되었습니다.

화면 수는 몇 안 되어도 33초 애니메이션을 만들기란 쉽지 않았습니다. 디자이너도, 개발자도 처음 시도해보는 작업물이었기에 함께 구현 방법을 고민하고, 매일 합을 맞추어야 했죠. 그렇게 두 달을 고생해서 배포한 온보딩 덕분에 전환율이 개선되었고, 서비스 측면에서는 인터랙션 웹 기술에 대한 첫 걸음을 내딛을 수 있었습니다.

이제 좀 리팩토링하며 쿨다운 기간을 가지려나 했는데, 끝남과 동시에 리브랜딩 TF팀에서도 인터랙션 웹페이지 제작이 필요하다는 소식을 들었습니다. 저는 평소에 기존 피플펀드의 홈페이지를 빨리 바꾸고 싶었던 니즈가 있었는데요. 새로운 심볼과 함께 기하학적인 인터랙션 표현을 구사할 수 있는 프로젝트라 하니 참여하지 않을 이유가 없었습니다. 그렇게 TF팀과 여러 홈페이지를 제작했고, 그 중 가장 공을 들였던 메인 페이지 개발 과정을 소개하려 합니다.

직전 프로젝트(이하 a)에서 인터랙션웹의 성공적인 도입으로 자신감을 얻은 저는 리브랜딩 프로젝트(이하 b)도 자신있었습니다. 하지만, 그것은 큰 오만이었죠. 애니메이션 플레이 방식에 큰 차이점이 있었습니다.

  • (a) 타임 기반 : 상영관에서 멈출 수 없는 영화처럼 개발자 의도대로 오브젝트를 원하는 위치와 시점에 등장
  • (b) 스크롤 기반 : 유저 이벤트 발생 시 애니메이션 플레이

또한 스크롤 기반 인터랙션은 무조건 풀페이지 표현이 좋다고 생각했지만, 동료 UT(Usability Testing)를 통해 유저가 스크롤잭킹을 경험할수록 불쾌감이 있고, 빠르게 넘겨버리면 풀페이지 특성상 아무것도 표시되지 않을 수 있다는 것을 알게 되었습니다.

이 이슈를 어떻게 돌파할지 고민하며 레퍼런스를 찾아보던 중, 애플 상품 소개 페이지를 살펴보았습니다. 같은 풀페이지 인터랙션임에도 어색함이 없던 이유는 스크롤 다운 시 사진 수백 개를 렌더링하며 마치 동영상이 플레이되는 것처럼 구현되어있었기 때문이었습니다. 이에 모든 스크롤 위치마다 화면(frame)을 정의해야 할 필요성을 깨닫게 되었죠.

애플 상품 페이지

이때, 조건문으로 불연속 frame을 정의해도 여전히 버벅임 현상이 잔재해, mapping 함수를 이용해 연속 frame을 정의했습니다.

다음은 현재 스크롤 위치에서 플레이할 action 함수를 mapping 해주는 로직입니다.

  • 전체 스크롤 길이를 1, 현재 스크롤 위치를 P_x (0~1)라고 해봅시다.
  • 그 다음, 인터랙션 action 비율을 정의합니다.
  • 이제, P_x 위치에서 아래 수식을 만족하는 minimum action index를 구합니다.

자, 로직을 세웠으니, 코드로 작성해보겠습니다. 기본적인 스크롤 및 애니메이션 기능은 framer-motion 라이브러리를 사용했습니다. 자주 사용한 함수는 다음과 같습니다. [참고 문서]

  • useTransform(target, arrayA, arrayB) : 맵핑함수, arrayA 를 arrayB 범위로 치환해준다.
  • useMotionValueEvent : MotionValue 는 애니메이션 값의 상태와 속도를 가진 객체로서, 이 값의 변화를 트래킹할 수 있는 훅이다.

<코드>

// 1. action별 비율과 handler 연결
const animationRatio: {
		times: number;
		animationHandler: AnimationHandler<AnimationControlObjects>;
	}[] = [
		{ times: 0.5, animationHandler: playAction1 },
		{ times: 0.2, animationHandler: playAction2 },
		{ times: 0.3, animationHandler: playAction3 },
	];

// 2. action index 주요 로직
const actionInfo = useTransform(
		scrollYPercent, // 현재 위치 P_x
		[0.5, 0.7, 1], // 누적된 P_m
		[1, 2, 3], // action index
	);

// 3. index, percent를 구해 action을 실행합니다. 
useMotionValueEvent(actionInfo, 'change', (latest) => {
	const idx = Math.floor(latest);
	const percent = latest - idx;

	animationRatio[idx].animationHandler({ percent, controls });
});

위 구조 덕분에 애니메이션 길이 조정을 쉽게 할 수 있었습니다. times 상수만 늘려주면 되기 때문이죠.

mapping 함수도 만들었겠다, 이제 심화 버전으로 가볼게요. 해당 섹션에서는 0% > N% 등의 비율 변화를 적합하게 표현하기 위해 파이 애니메이션을 채택했습니다.

파이 움직임이 마치 타이머 움직임과 동일하다고 생각이 들자, 비교적 쉽게 움직이는 파이를 구현할 수 있었습니다. 문제는, ‘스크롤 위치와 타이머 채우기 비율을 어떻게 mapping할 수 있을까?’ 였습니다. (이해하기 쉽도록, 75% 까지 차오르기를 예시로 들어볼게요)

기능 명세
1. 0도 → 179도 파이 회전
2. 180도 직전에서 텍스트 사라지기
3. 180도에서 잠깐 머무른 뒤, 0% 텍스트 등장하기
4. 270도까지 회전하면서 75% 텍스트 차오르기

혹시 위 기능 명세에서 공통점을 찾으셨나요? 역시 정답은 지문에 있었습니다. 이전에는 스크롤 위치에 액션을 매칭했다면, 이번에는 회전되는 도수에 액션을 연결하기가 핵심이었습니다.

코드를 살펴볼게요. 혹시 ‘기능 명세에서는 분명 4개의 break 지점이 (0도, 180도 직전, 180도, 270도) 정의되어 있는데, 왜 5가지가 필요할까?’ 라고 의문이 드셨다면, 이는 ‘180도에서 잠깐 머무르기’ 트릭이 필요하기 때문입니다. ROTATE180 전후로 0.2 ~ 0.6 약 0.4 비율을 차지함으로써 마치 머무르기 효과를 구현할 수 있었습니다.

// 1. action별 비율과 handler 연결
...생략

const beforeDegTo사라지기_Text = 0.001;
const afterDegTo등장_Text = 0.001;

// 2. 맵핑 로직
const transformedDeg = useTransform(
	scrollYProgress,
	[0, 0.2, 0.5, 0.6, 1],
	[0, ROTATE180 - beforeDegTo사라지기_Text, ROTATE180, ROTATE180 + afterDegTo등장_Text, 270],
);

useMotionValueEvent(transformedDeg, 'change', (latest) => {
	// 3. 현재 회전 도수(deg)에 필요한 action을 실행합니다.
});

구현이 마무리되었다 싶을 즈음 최종 난관이 남아있었습니다. 바로 ’스크롤 감도 통일하기’였는데요. UT 분석 결과 유저의 스크롤 습관과 마우스인지, 트랙패드인지에 따라 전달되는 감도 차이가 너무 달랐습니다.

  • 유저1 : 빠르게 넘겨본 뒤, 다시 올라가며 궁금했던 구간을 되짚어봄
  • 유저2 : 문구 하나하나 읽어보며, 애니메이션 잔상까지 확인됨

우선, 트랙패드/마우스 기기별 속도감 차이는 통일할 수 없다고 판단했습니다. 유저별로 마우스 설정값이 다를뿐더러 스크롤 양은 조절할 수 없기 때문입니다. 다만, ‘적어도 예상되는 인터랙션 전환을 보여주자’를 목표로 이슈를 해결했습니다. 객관적인 기준을 두고자 인터랙션 전환마다 몇 개의 frame을 가지고 있는지 일일이 수를 셌습니다. 해당 페이지는 방향키로도 스크롤 이동이 가능했기에, 1 down = 1 frame으로 기준을 잡았습니다. 같은 인터랙션임에도 서로 다른 frame 수를 가지고 있음을 확인했고, 이를 조정해서 1차 감도를 맞출 수 있었습니다. 그리고 특정 마우스를 기준으로 섹션 간의 감도를 통일한 뒤, 전체적인 스크롤 길이를 조정하는 것으로 디테일을 잡았습니다.

마지막으로, 부드러운 애니메이션을 위해 브라우저 렌더링 순서(layout → paint → composite)를 고려해 width 늘리기보다, scale 속성을 사용하는 등의 최적화를 진행했습니다.

그렇게, 스크롤 감도를 통일해서 인터랙션 홈페이지를 완성할 수 있었습니다.

몇 달 동안 같은 시안을 보고 개발하다보니, 오며 가며 참 많은 동료분들이 QA를 해주셨는데요. 개발 초창기 당시, ‘어? 이거 맞아요?’, ‘스크롤을 제어할 수 없는 게 답답해요’ 등 객관적인 피드백을 해주신 덕분에 풀페이지를 버리고 스크롤 mapping 방법으로 틀 수 있었습니다. 그 뒤로도 새로운 홈페이지를 위해 적합한 문구와 인터랙션을 제안해주셨고요.

완성된 페이지에 대해 “한 편의 영화를 본 것 같다.” 라는 말을 들었을 때는 눈물이 날 것 같았습니다. 끝없는 이슈에 지쳐가고 있을 즈음, 동료의 칭찬이 너무 큰 힘이 되었기 때문이죠.

서로 지칠 때 힘이 되어준 동료의 응원

돌아보니 멋진 결과물을 얻기까지 참 많은 분들이 함께 해주셨어요. 아낌없는 조언과 도움을 주셨던 Web Team 자올님, 웹 구현이 어려운 구간은 기꺼이 협업을 수락해주신 Design Team 현기님과 여러 리브랜딩 TF 팀원분들이 있었기에 완성할 수 있었습니다. 무엇보다 두 번의 인터랙션 개발을 함께해 짱친이 된 Design Team 범준님! 앞으로도 잘 부탁드립니다.

Design Team과 함께 고민을 나눈 흔적…

끝으로, 테크 블로그 작성을 제안 받았을 때 너무 기뻤습니다. 입사 초기에 테크 블로그 활성화를 선언했지만, 약속을 이행하지 못했기 때문이죠. 이제 시작입니다! 앞으로도 PFCT 훌륭한 개발자분들의 컨텐츠를 기대해주세요!

written by Seonmi
edited by Gaeun
designed by Yelin


새로워진 PFCT와 함께해요