속닥속닥은 어떻게 UX를 향상시켰을까요?

movie·2022년 10월 26일
1
post-thumbnail

🙌🏻 User Experience

UX란 서비스를 사용하는 사용자가 겪는 다양한 경험/감정을 말합니다.

저희 속닥속닥은 기획 당시부터 대규모 트래픽 경험을 원했고,
커뮤니티 서비스 특성상 사용자가 존재하지 않으면, 서비스가 존재하는 목적이 희미하다고 생각했습니다.
이런 이유로 최대한 사용자를 끌어드리는 것이 가장 큰 목표 중 하나였습니다.

사용자를 잠깐동안 유입시키는 것은 생각보다 쉬운 일입니다.
여러 서비스들이 많은 돈을 투자하여 광고를 진행하는 것이 이와 같은 맥락이겠죠.
하지만 이 유입된 사용자를 유지시키는 것이 가장 어려운 일이라고 생각합니다.

사실 제가 생각해도 사용자를 전혀 배려하지 않는 UI를
사용해야만 하는 입장이라면 그 서비스를 이용해야하는 목적이 생기기만 해도 피곤해지는 것 같습니다.
목적을 달성하기 위한 어떻게 보면 UI는 작은 수단일뿐인데 이 때문에 목적을 달성하는 것 자체가 피곤해지기 때문입니다.

예를 들어, 어떤 제품을 구매해야하고, 그 제품이 특정 서비스에서만 판매되고 있다고 가정했을 때
사용자는 어쩔 수 없이 특정 서비스를 이용해야합니다.
하지만 이 서비스의 UI가 사용자의 입장에서 제작되지 않았다면, 사고 싶은 물건을 구매하더라도 사용자는 큰 만족감을 느끼지 못할 것 입니다.

사용자의 투자 대비 수익이 낮으므로 저희 서비스에 대한 수요는 낮아지게 되겠죠.

제 경험에서 얘기드리자면, 저는 어떤 서비스를 꼭 사용해야하는 상황이였습니다.
하지만 그 서비스는 웹에서 속도가 너무 느리고, UI가 깨지는 문제가 발생하여 버튼을 누르지 못해 상품을 구매할 수 없는 상황이였습니다.
결국에 해당 서비스의 어플리케이션을 다운로드 했고, 웹으로는 이용하지 않게 되었던 적이 있습니다.

이런 이유로 사용자를 서비스에 유지시키기 위해서는 UI/UX를 꼭 신경써야 한다고 생각합니다.

사용자는 새로운 경험을 위해 기존 경험을 활용합니다.

저희는 디자인 작업에 프론트엔드 / 백엔드가 모두 참여하였는데요.
저희 팀에는 디자이너가 존재하지 않았기 때문에 레퍼런스를 참고하는 일이 다수였습니다.
여러 익명 커뮤니티를 조사한 결과 UI의 전체적인 틀이 비슷하다는 생각이 들었습니다.

여기서 제이콥의 법칙을 생각해볼 수 있습니다.
사용자는 새로운 경험을 이해하기 위해 기존 경험을 활용한다고 합니다.
여러분이 쇼핑 사이트를 사용하고 있다면, 장바구니 페이지로 이동하는 버튼은 어디에 있다고 예상하시나요?

일반적으로 대부분의 쇼핑 사이트에는 장바구니 페이지로 이동하는 버튼이 우측 상단에 존재합니다.
서비스를 새로 만들어 냈는데 다른 서비스들과 차별화를 두고 싶은 마음에
장바구니 버튼을 특별한 위치에 두면 어떨까요?
특별해지고자 하는 저희의 마음과는 달리 사용자는

어 뭐야 ! 장바구니 버튼 도대체 어딨어? 라고 하며 사용에 불편함을 느낄 것 입니다.

이런 이유로 저희는 UI의 기본적인 큰 틀은 다른 커뮤니티 사이트를 따라가도록 노력했습니다.
즉, ‘버튼이 존재할만한 위치에 버튼을 배치’하도록 했습니다.

상호작용 가능 대상의 거리가 너무 좁다면 이는 버그로 취급될 수 있습니다.

제가 웹 서비스를 사용하면서 가장 불편하다고 느끼는 점 중에 하나는 터치 대상 사이에 거리가 너무 좁을 때입니다.

물론 개발자는 자신이 계획한대로 구현해냈다고 생각할 수 있습니다.
하지만 터치 대상 사이의 거리가 너무 좁을 때 사용자는 조금만 거리 조절을 실패한다면
의도한 행동이 아닌 다른 행동을 수행하게 됩니다.

컴퓨터는 잘못이 없다는 말이 있습니다.
컴퓨터는 그저 들어온 입력에 대한 올바른 출력을 했을 뿐입니다.
하지만 사용자는 의도치 않은 결과물을 얻게 되면서 이를 버그로 느꼈을 확률이 큽니다.

저희는 상호작용할 수 있는 대상의 범위를 확보하기 위해 노력했습니다.

속닥속닥 서비스의 글 상세보기 페이지입니다. 본인이 작성한 글일 경우 상단에 수정, 삭제 버튼이 표시됩니다.
const controllerButton = (props: { theme: Theme }) => css`
	...
  
  padding: 5px;  
`;

export const UpdateButton = styled.button`
  ${controllerButton}
`;

export const DeleteButton = styled.button`
  ${controllerButton}

  color: ${props => props.theme.colors.red_200};
`;

위의 코드는 게시글을 수정 삭제할 수 있는 버튼에 대한 스타일인데요.

controllerButton이라는 스타일을 공통 스타일로 사용하고 있는 간단한 코드입니다.

여기서 집중해야 하는 부분은 두 버튼을 떨어뜨리기 위해 margin이 아닌 padding을 사용하고 있다는 것입니다.

디자인을 위해 두 버튼을 떨어뜨려줌과 동시에 버튼을 클릭할 수 있는 범위가 늘어나므로
사용자가 특정 버튼을 눌렀을 때 의도치 않은 결과가 나올 확률이 적어집니다.

인간은 절정의 순간과 마지막 순간에 느낀 감정으로 경험을 판단합니다.

인간은 긍정적인 순간보다 부정적인 순간을 더 생생하게 기억한다고 합니다.

어렸을 때 가장 충격적인 사건이 무엇이었나요? 라고 누군가 묻는다면
저는 ‘쇄골이 부러졌을 때요..’ 라고 할 겁니다. 그때의 아픔은 시간이 많이 흘렀지만 잊혀지지 않습니다. 너무 아팠기 때문이죠.
이에 반해 행복하고 천진난만했던 시절은 기억이 가물가물하고, 사진을 보지 않는 이상 잘 떠오르지 않습니다.

이처럼 부정적인 순간이 좀 더 크게 와닿게 되는 것이 있습니다. 이를 피크엔드 법칙이라고 하는데요. 인간은 절정의 순간과 마지막 순간에 느낀 감정으로 경험을 판단한다고 합니다.

그렇다면 사용자가 서비스를 이용하면서 가장 크게 만날 수 있는 부정적인 절정의 순간은 언제일까요?
바로 서비스를 정상적으로 이용할 수 없다는 결과가 나오는 화면입니다.

헉!!! 스트레스 😡

이런 오류 결과 페이지에 유머를 활용하면 더 즐거운 경험으로 바꿀 수 있는 기회로 삼을 수 있습니다.

속닥속닥은 사용자의 스트레스를 조금이라도 줄이고자 404 에러시 귀여운 판다로고를 보여주고, 애니매이션을 추가했습니다.

이를 통해 사용자가 느낄 수 있는 부정적인 감정을 최소화시켜 좋은 UX를 만들어낼 수 있습니다.

핵심 동작은 시각적으로 눈에 띄어야 합니다.

폰 레스토프 효과에 따르면 비슷한 사물이 여러개 있을 때 그 중에서 가장 차이가 나는 한가지만 기억할 가능성이 크다고 합니다.

개발자가 사용자에게 이끌어내고 싶은 플로우가 있다면 색상적으로 강요하는 것이 유리하겠죠?

저희는 이를 활용하여 사용자 플로우를 좀 더 매끄럽게 하는 것에 대해 UX를 개선하였습니다.

저희 서비스에서 신고 버튼을 눌렀을 때 화면에 표시되는 모달입니다.
우측 하단을 보면, 신고 로직을 취소하거나, 신고를 작성 완료하는 확인 버튼이 존재합니다.

딱 사진만을 보았을 때 확인 버튼이 강조되나요? 취소 버튼이 강조되나요?
대부분의 독자들은 ‘확인’ 버튼이라고 말할 것 입니다.

사용자가 신고 버튼을 눌렀을 때 기대하는 플로우가 무엇일까요?
저는 신고를 하기 위해서 이 모달을 띄웠기때문에
신고가 완료되는 것이 사용자의 목표라고 생각합니다.

이런 이유로 사용자에게 이끌어 내야할 플로우가 ‘확인 버튼을 눌러 신고를 진행한다’라고 할 수 있습니다.

만약 ‘확인’ 버튼이 강조되어 있지 않으면 사용자 입장에서는 두 개의 버튼이 존재할 때 어떤 버튼을 클릭해야하는지 한번 더 생각하는 과정을 거쳐야 합니다. 사용자의 목표가 ‘확인’ 버튼임에도 불구하고 말이죠!

그래서 저희는 확인 버튼을 취소버튼보다 강조하여 사용자가 목적에 좀 더 쉽게 도달할 수 있도록 합니다.

모달을 뒤로가기로 닫을 수 있다면 편할 것 같아요.

속닥속닥에서는 사용자가 검색창에 접근하면 모달이 띄워집니다.

보통의 모달창은 아래에 dimmer를 깔아 dimmer만 클릭하더라도 모달창이 닫힐 수 있게 합니다.

하지만, 저희의 검색창은 창 전체가 화면을 차지하도록 구현되어 있습니다.
사용자가 이를 닫기 위해서는 ‘취소’버튼을 찾아서 닫는 방법뿐이죠.

저는 보통 서비스를 이용할 때 dimmer가 존재하지 않는다면 뒤로가기를 통해 현재 화면을 벗어나려고 합니다.
왜냐하면 ‘취소’버튼을 찾는 일은 꽤 귀찮은 방법이기 때문이죠.

또, 요즘 모바일 기기는 스와이프(swipe)를 통한 뒤로가기를 지원합니다. 사용자 입장에서는 ‘취소’ 버튼을 찾는 것보다는 화면을 휙 쓸어버리는 것이 편하고 빠릅니다.

하지만 모달창같은 경우엔 새로운 history가 쌓이지 않고 그저 현재 화면을 덮는 역할을 합니다. 즉 뒤로가기로 모달을 닫을 수 없습니다.
저는 여기서 큰 불편함을 느끼게 되었습니다.

이를 해결하기 위해 모달에 history를 주어 뒤로가기를 통해 닫을 수 있도록 구현했습니다.

처음에는 popstate 이벤트를 사용하여 popstate 이벤트가 발생했을 때 모달 닫기 로직을 실행해주려 했습니다.

즉 뒤로가기 이벤트가 발생했을 때 모달를 닫으려고 했었는데요,

하지만 popstate 이벤트는 e.preventDefalut()가 불가능한데요. 즉, 기본 동작 방지가 불가능합니다.

이유는 calcelable 속성이 No이기 떄문입니다.

그래서 모달을 직접적으로 history에 추가하는 방법을 채택했습니다.

우선 모달을 history에 추가해야하는 이유는 이렇습니다.

  • 모달이 history에 추가되지 않을 경우 뒤로가기를 통해 모달을 닫을 수는 있지만, 뒤로가기 이벤트로 인해서 모달이 띄워진 페이지가 아닌 그 전 페이지로 이동하게 됩니다.
  • 만약 사용자가 처음 웹 사이트에 방문하여 history가 쌓여있지 않다면 사용자는 뒤로가기를 통해 모달을 닫을 수 없습니다.

모달을 history에 추가 해주기 위해 history.pushState를 활용했는데요!

const useModalHistory = ({ closeModal }: UseModalHistoryProps) => {
  const popModalHistory = () => closeModal();

  useEffect(() => {
    history.pushState(null, ''); // (1) 모달을 history에 넣습니다. 
    window.addEventListener('popstate', popModalHistory); // (2) 모달이 render되면 닫기 이벤트를 추가합니다. 

    return () => window.removeEventListener('popstate', popModalHistory);
  }, []);
};

이를 추후에 재사용해주기 위해 hook으로 분리했습니다.
로직은 아래와 같은데요.

  1. 사용자가 모달창을 여는 순간 history.pushState를 통해 history에 모달을 추가해준다.
  2. 만약 사용자가 모달에서 뒤로가기를 누르는 순간 popstate 이벤트 핸들러인 closeModal()이 실행된다.
  3. 임의로 추가된 history가 pop된다.

이로써 사용자는 뒤로 가기를 통해 손쉽게 모달을 닫을 수 있게 되었습니다! 🙂
저 또한 서비스의 사용자로서 너무 편리함을 느꼈습니다.

반응형을 도입하여 모든 환경을 만족시킵니다.

속닥속닥은 기획 당시 모바일 기반을 목표로 했습니다.

최초 디자인 시안을 보면
모바일 환경이라 가정하고 제작을 진행한 것을 볼 수 있습니다.

from: statcounter

저희는 현재 모바일 시장 점유율이 높기 때문에
당연히 단순한 커뮤니티 사이트는 모바일로 접속할 확률이 크다고 생각해, 모바일 환경을 우선 구현하였습니다.

하지만, 저희가 간과했던 사실이 하나 있습니다.
저희 주고객은 우아한테크코스 크루들이고, 이들은 데스크탑, 노트북을 하루 몇시간씩 이용하는 고객들이라는 점입니다.

저희 서비스 모니터링 툴 결과만 봐도 이를 확인할 수 있습니다.

from: jenniferfront

위는 10월 1일부터 26일까지 측정한 결과물입니다.
Desktop 환경이 약 60%, Mobile 환경이 약 35%를 차지 합니다.

모바일 환경을 열심히 구현하고 있을 때, 저희는 모바일에 최적화된 화면에 불편함을 느끼는 사용자가 있다는 것을 알아챘습니다.
이를 해결하기 위해 데스크탑을 대응할 수 있도록 반응형을 도입하게 되었습니다.

모바일 기준 서비스에서 데스크탑 환경을 지원해주기 위해서는 꽤 많은 리소스가 필요했습니다.
데스크탑의 넓은 화면을 채우기 위해서는 추가적인 컨텐츠를 넣어야했기 때문입니다.

이 추가적인 컨텐츠를 렌더링하는 기준을 측정하기 위해 새로운 hook을 추가했습니다.

const useResponsive = (size: number) => {
  const [isSizeOver, setIsSizeOver] = useState(() => window.innerWidth > size);

  const handleResize = useThrottle(() => {
    if (size < window.innerWidth) {
      setIsSizeOver(true);
    }
    if (size > window.innerWidth) {
      setIsSizeOver(false);
    }
  }, 100);

  useEffect(() => {
    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return isSizeOver;
};

useResponsive hook은 지정한 사용자가 resize를 진행하면서 특정 기준을 벗어나게 되면 이를 알려주는 hook 입니다.

size 인자를 875px(태블릿 크기)로 주었다고 가정했을 때
window.innerWidth가 이보다 커지면 반환값을 true로 주어 컨텐츠를 추가하고 제거할 수 있도록 했습니다.

<Container>
	{isSizeOver && <ForDesktop />}
</Container>

사용하는 곳에서는 이와 같이 사용할 수 있습니다.

또, 여기서 하나의 문제를 추가로 직면했는데요.
rem, em 관련된 문제입니다.

em, rem 둘 다 font-size 속성 값에 비례해서 결정되는 상대 단위를 말합니다.

rem : 최상위 요소의 font-size를 기준
em : 해당 단위가 사용되고 있는 요소의 font-size 속성 값이 기준

예를 들어,

html {
	font-size: 16px;
}

/* em */
div {
	font-size: 20px;
  width: 10em; /* 200px */
}

/* rem */
div {
  font-size: 20px;
  width: 10rem; /* 160px */
}

html 태그의 font-size가 16px 일 때 하위 div의 width을 10rem으로 설정해주면
16×10=16016 × 10 = 160, 즉 160px이 됩니다.

html 태그의 font-size가 16px 이, 하위 div의 font-size가 20px이라면
하위 div의 width을 10em으로 설정해주면
20×10=20020 × 10 = 200, 즉 200px이 됩니다.

반응형에서 rem, em이 등장하는 이유는 반응형을 구현하는데 rem, em의 특성이 이를 쉽게 해주기 때문입니다.
이 특성을 이용하여 html 태그의 font-size만 변경해주면, 다른 하위 요소에서 rem으로 작성한 사이즈들이 모두 그에 맞춰 변경됩니다.

코드로 확인하자면,

html {
	font-size: 10px;

	@media (min-size: 875px) {
		font-size: 20px;
	}

	@media (min-size: 1024px) {
		font-size: 30px;
	}
}

div {
	width: 1rem;
}

div 태그는 화면 사이즈가 달라졌을 때마다 html 태그의 font-size와 media 쿼리에 의존하여 자동으로 width가 변경되게 됩니다. 모든 하위 태그에 media 쿼리를 달아주지 않아도 된다는 점에서 이 특징은 반응형 도입을 빠르게 할 수 있습니다!

현재 viewport의 width가 875px보다 적다면 div의 width는 10px, 875px보다 크고 1024px보다 작다면 width는 20px, 1024px보다 크다면 width는 30px이 됩니다.

하지만 저희 팀은 이를 반영하지 못합니다. 문제가 있었기 때문이죠.

rem 의 장점은 반응형 도입에서만 있는 것이 아닙니다.
보통 브라우저 default 폰트 사이즈는 16px입니다.
하지만, 사용자가 임의로 브라우저 기본 폰트 사이즈를 변경할 수 있습니다.
html 태그는 font-size를 명시 지정해주지 않았다면, 브라우저 설정된 값을 상속받기 때문에 사용자가 브라우저 폰트 사이즈를 변경했을 때 영향을 받습니다.
저희는 이런 점을 이미 알고 있었기 때문에 반응형 작업을 도입하기 전에 rem을 사용했습니다.
rem을 사용하므로써 사용자가 브라우저 폰트 사이즈를 변경해도 적절한 UI를 유지할 수 있었죠.

하지만 이게 반응형 도입할 때 문제가 되었습니다.
이미 rem을 사용하고 있었기 때문에 위 방식을 사용하기엔 이미 지정되어 있는 rem 수치를 모두 손 봐주어야 하기 때문입니다.

또 위 방식은 개발자의 계산을 쉽게하려면 html 태그의 font-size를 10단위로 해주는 것이 좋습니다.
하지만 이미 rem이 쓰인 부분은 default값인 16px을 기준으로 계산을 했기 때문에 10단위로 변경하는 순간 계산을 다시 해야 했습니다.

그래서 em을 사용했는데요.
em을 사용하여 컴포넌트안 최상위 태그인 container의 font-size를 기준으로 하위 요소의 크기를 조정하도록 했습니다.
이 방식을 통해 rem만큼은 아니지만 media 쿼리의 사용을 최소화할 수 있었던 것 같습니다.

참고

profile
영화보관소는 영화관 😎

1개의 댓글

comment-user-thumbnail
2024년 5월 30일

For anyone interested in seeing these improvements in action, Click here to watch how Whisper Whisper has revolutionized movie UX. It's fascinating to see how thoughtful design and user-centric features can transform the way we enjoy our favorite films.

답글 달기