무조건 스켈레톤 화면을 보여주는게 사용자 경험에 도움이 될까요?

무조건 스켈레톤 화면을 보여주는게 사용자 경험에 도움이 될까요?

시작하며

안녕하세요, 카카오페이 FE인프라TF 에릭입니다.

우리는 사용자 경험 향상을 위해 비동기 데이터 요청과 같은 로딩 상황에서 사용자에게 스켈레톤 화면을 보여주곤 합니다. 이런 로딩 상황에서 무조건 스켈레톤을 노출하면 사용자 경험이 향상될까요?

Progress Indicator에 대한 UX 리서치

세계적인 UX 리서치 그룹 닐슨 노먼은 Progress Indicator에 대한 중요성을 강조하며 아래와 같은 주요 지침을 이야기 하였습니다.

좋은 Progress Indicator는 사용자에게 긍정적인 경험을 줄 수 있고 사용자가 작업을 끝까지 수행할 수 있도록 만들 수 있습니다.

Progress Indicator와 관련된 주요 지침은 다음과 같습니다.

  1. 약 1초 이상 걸리는 작업에는 Progress Indicator를 사용하십시오.
  2. Loop Animation은 빠른 동작에만 사용하십시오.
  3. Percent-done Animation은 10초 이상 걸리는 작업에 사용하십시오.
  4. Static Indicator는 사용하지 마십시오.

로드하는데 1초 미만이 소요되는 모든 항목의 경우 반복되는 애니메이션을 사용하면 주의가 산만해집니다. 사용자는 화면에서 어떤 일이 발생했는지 따라갈 수 없고, 화면에 깜빡이는 내용에 대해 불안을 느낄 수 있습니다.

출처: https://www.nngroup.com/articles/progress-indicators/

위 지침을 한번 잘 생각해봅시다. 우리는 Progress Indicator를 잘 사용하고 있을까요?

카카오페이의 대부분 서비스에서는 로딩 상황에 사용자에게 정적인 화면을 보여주는 대신 스켈레톤 애니메이션을 화면에 보여줌으로써 사용자의 요청이 진행 중임을 표시하고 있습니다. 더 나아가서 화면 요소별로 점진적으로 스켈레톤 애니메이션을 보여주는 등 사용자 경험 향상을 위한 Progress Indicator를 적극적으로 사용하고 있습니다.

아마 저희뿐 아니라 이 글을 읽으시는 여러분들도 사용자 경험 향상을 위해 “오래 걸리는 작업에 대한 Progress Indicator”를 충실하게 보여주고 계실 거라고 생각이 됩니다. 그런데 한국의 빠른 인터넷 속도와 Progress Indicator가 만나면 어떤 상호작용을 일어날까요?

위 지침의 “1초 이상 걸리는 작업에 Progress Indicator를 사용하십시오. 로드하는데 1초 미만이 소요되는 모든 항목의 경우 반복되는 애니메이션을 사용하면 주의가 산만해집니다.” 라는 부분을 생각해보면, 어쩌면 우리는 Progress Indicator 때문에 역설적이게도 사용자에게 나쁜 경험을 주고 있지 않을까요?

카카오페이 공지사항의 사례

카카오페이의 “공지사항” 화면은 FE로 제공되고 있습니다. 이 화면을 구성하기 위해서 우리는 “전체 카테고리 목록”과 “카테고리별 공지사항 목록”, 2개의 API를 사용하고 있습니다.

이 API들은 특별한 비즈니스 로직을 포함하지 않고 있기 때문에, 사용자 환경이 양호하다면 API 응답 지연시간이 거의 없을 겁니다. (물론 사용자 환경이 양호하지 않은 경우에는 당연히 응답 지연시간이 오래 걸리겠지만요 😂)

🙇‍♂️ 참고: 실제 사용자에게 제공되는 페이지에는 API Caching이 적용되어 있지만, 이 포스팅의 내용을 더 잘 표편하기 위해 아래 첨부된 모든 화면에는 API Caching이 제거되어 있습니다.

API의 지연 시간이 100ms과 300ms인 두 가지 케이스가 있다고 가정해 봅시다. 사용자들이 느끼는 경험에는 어떤 차이가 있을까요?

지연시간 100ms
지연시간 300ms

저는 API 지연 시간 300ms인 화면에서는 “즉각적으로 무언가를 불러오고 있다” 는 느낌을 받았습니다. 스켈레톤을 통해 웹 페이지와의 상호작용이 빠르고 쾌적하다는 생각도 들었습니다. 하지만 API 지연 시간이 100ms인 경우는 어떤가요? 실제로 제가 요청한 화면은 더 빠르게 노출되었지만 스켈레톤이 화면에 보여지는 시간이 너무 짧았기 때문에 되려 거슬리는 인상을 받았습니다. 중간 화면이 스켈레톤임을 인지하지 못하고 봤다면 화면이 깨진 느낌, 혹은 살짝 덜그럭거리는 느낌을 받았을 것 같습니다.

이런 경우에는 오히려 사용자에게 스켈레톤을 보여주지 않는 게 나을수도 있지 않을까요? 아래 이미지는 응답 지연 시간이 100ms이고 로딩 중 스켈레톤을 노출하지 않은 화면입니다.

지연시간 100ms

기존의 스켈레톤이 덜그럭거리는 화면과 비교하면 이 화면은 조금 더 자연스러운 느낌을 줍니다. 비록 API를 불러오는 시점에 스켈레톤이 노출되지 않지만 그 시간이 100ms으로 아주 짧기 때문에 하얀 화면이 중간에 뜨는 부분이 어색하지 않게 느껴지는 것이죠.

공지사항 API는 평균적으로 60ms 전후의 지연시간을 보여주고 있습니다. 만약 이 정도 지연시간이 보편적이라고 생각하면 “API 응답 시간이 짧은 경우에는 스켈레톤이 보여지지 않게끔” 하는 기능을 넣어주는게 오히려 더 좋은 사용자 경험을 줄 수 있지 않을까요?

React Suspense와 함께 구현하기

카카오페이 개발 블로그에서 자주 언급되는 Suspense를 사용한 데이터 불러오기를 사용하면 이런 복잡한 UI 케이스를 쉽게 구현할 수 있습니다.

물론 Suspense를 사용하지 않고도 아래와 같은 결과를 쉽게 얻을 수 있겠지만, 모든 컴포넌트 내부에 setTimeout을 사용하거나 Custom Hook으로 화면을 제어하게 되면 코드가 복잡해지고 유지보수가 어려워질 수 있습니다.

CSS를 사용해서 동일한 결과를 얻을 수 있지만, 여기서는 CSS 대신 Javascript를 사용해서 구현하겠습니다.

우선 늘 우리가 하듯 API를 호출하는 CategoryList 컴포넌트를 Suspense로 감싸 스켈레톤을 보여줍시다.

참고: CategoryList Component에서는 React Query의 Suspense Option을 사용하여 데이터를 불러옵니다.

// ...전략
<Route exact path={ROUTE.CATEGORY_LIST}>
  <Suspense fallback={<HomeSkeleton />}>
    <CategoryList />
  </Suspense>
</Route>
// ...후략

그리고 로딩이 시작된 후 특정 시점까지는 스켈레톤을 보여주지 않기 위한 용도로 사용할 유틸성 컴포넌트를 하나 만들어줍니다. 아래 DeferredComponent는 children을 Props로 받고, 200ms이 지나기 전에는 children을 화면에 렌더하지 않는 컴포넌트입니다. Suspense의 fallback으로 내린 스켈레톤을 이 컴포넌트로 한번 감싸 특정 시점이 지난 후 스켈레톤을 사용자에게 노출시키도록 구성하겠습니다.

const DeferredComponent = ({ children }: PropsWithChildren<{}>) => {
  const [isDeferred, setIsDeferred] = useState(false);

  useEffect(() => {
    // 200ms 지난 후 children Render
    const timeoutId = setTimeout(() => {
      setIsDeferred(true);
    }, 200);
    return () => clearTimeout(timeoutId);
  }, []);

  if (!isDeferred) {
    return null;
  }

  return <>{children}</>;
};
// ...전략
<Route exact path={ROUTE.CATEGORY_LIST}>
  <Suspense
    fallback={
      <DeferredComponent>
        <HomeSkeleton />
      </DeferredComponent>
    }
  >
    <CategoryList />
  </Suspense>
</Route>
// ...후략

이렇게 변경한 다음에는 어떤 사용자 경험을 주는지 한번 볼까요? 아래는 API 응답 지연시간이 100ms인 경우와 1000ms인 경우의 화면입니다.

지연시간 100ms
지연시간 1000ms

DeferredComponent 내부의 지연 시간을 200ms으로 잡았기 때문에, 응답 지연이 100ms인 경우에는 스켈레톤을 보여주는 대신 사용자에게 잠시 빈 화면이 노출된 뒤 화면이 제공됩니다. 응답 지연이 1000ms인 경우에는 200ms 동안은 빈 화면이 뜨고 나머지 800ms 동안 스켈레톤이 노출됩니다. 제가 느끼기엔 200ms의 하얀 화면이 그리 어색하게 느껴지지는 않는데, 여러분들은 어떠신가요?

물론 이렇게 지연 시간을 설정한 경우에도 덜그럭 거리는 스켈레톤 뷰를 완벽히 피할수는 없습니다. 예컨데 200ms까지 스켈레톤을 보여주지 않게끔 설정한 화면이 있다고 가정하였을 때, 만약 사용자에게 API 응답이 250ms, 또는 300ms만에 도착했다면 50~100ms 동안 노출될 스켈레톤이 사용자에게 똑같이 덜그럭 거리는 느낌을 주게 되겠죠.

그렇다면 이런 지연된 스켈레톤이 정말 아무 의미가 없을까요?

Firebase Performance Monitoring

저희 카카오페이 프론트엔드 팀에서는 Firebase Performance Monitoring으로 다양한 성능 지표를 수집하고 있습니다.

아래 화면은 모 서비스의 운영 환경 네트워크 요청 응답 시간 지표입니다. 이 서비스를 사용하는 사용자들은 평균적으로 110ms의 지연 이후 API 응답을 받고 있음을 알 수 있습니다.

파이어베이스 캡쳐 - 개요
파이어베이스 캡쳐 - 개요

파이어베이스 캡쳐 - 세부 - 75%
파이어베이스 캡쳐 - 세부 - 75%

세부 정보를 보니 75%의 사용자들은 192ms 이내에 응답을 받고 있네요. 만약 스켈레톤 화면을 200ms정도 지연시켜 사용자에게 노출한다면 75%의 사용자에게는 덜그럭 거리는 스켈레톤 뷰를 보여주지 않을 수 있습니다. 더 자연스러운 사용자 경험을 제공할 수 있다는 말과 같은 뜻이겠네요.

😅 정확히는 75%의 사용자가 아니고 75%의 API 요청입니다. 하지만 여기서는 모든 사용자가 비슷한 횟수의 API 요청을 수행한다고 가정하고 뭉뚱그려 “사용자”로 표기하였습니다.

앞서 이야기한 대로 지연된 스켈레톤이 사용자가 느끼는 “덜그럭거리는 스켈레톤”을 해결하지는 못합니다. 특정 시간만큼 지연시켜서 스켈레톤을 노출하여도 지연되고 나서 바로 로딩이 끝나는 경우에는 똑같이 덜그럭거리는 화면을 보게 되고, 사용자 경험이 떨어지게 될 겁니다.

“사용자는 100ms~200ms 정도의 스켈레톤 노출에서 덜그럭거림을 느낀다” 라고 가정해봅시다. 이 가정이 맞다면 스켈레톤 노출을 200ms 만큼 지연 시킬 경우 대략 300ms 정도의 지연 시간을 갖는 사용자들은 덜그럭거림을 느낄 수 있겠죠.

파이어베이스 캡쳐 - 세부 - 90%
파이어베이스 캡쳐 - 세부 - 90%

지금 이야기중인 이 서비스에서는 대략 90%의 사용자들이 296ms 이내에 응답을 받는다고 합니다. 이 수치를 토대로 생각해보면 스켈레톤을 200ms 지연시킬 경우 전체 사용자 중 75%는 기존에 느끼던 덜그럭거림을 느끼지 않을 수 있지만, 기존에 덜그럭거림을 느끼지 못하던 15%의 사용자들은 덜그럭거리는 스켈레톤 뷰를 보게 된다는 생각을 해볼 수 있을 것 같습니다.

(물론 실 서비스에서는 다양한 시나리오를 세워보고 고민하며, 실험과 검증을 통해 지속적으로 개선 프로세스를 진행해야겠지만) 위 가설이 참이여서 전체 사용자의 75%가 느끼던 덜그럭거림을 덜어내고 15%의 사용자에게만 덜그럭거림을 느끼게 한다면 마냥 손해보는 장사는 아닐 수 있겠다는 생각이 듭니다.

마치며

우리는 사용자 경험 향상을 위해 항상 모든 로딩 화면에 Progress Indicator(=Skeleton)을 사용하고 있습니다. 하지만 빠른 인터넷 환경을 사용중인 사용자에게는 오히려 Progress Indicator가 선택적으로 제공될 때 더 좋은 사용자 경험을 줄 수 있을 것 같습니다.

사용자에게 보여짐으로써 완결되는 우리 프론트엔드 서비스는 그 특성상 모든 사용자의 환경에서 동일한 경험을 제공하기 어렵습니다. 그러나 사용자 경험에 대한 연구들을 통해 다양한 인사이트를 얻고, 데이터를 활용한 가설 수립 및 검증 과정을 지속적으로 진행한다면 많은 사용자에게 동일하지는 않아도 더 좋은 사용자 경험을 제공할 수 있을 것 같습니다.

eric.dev
eric.dev

카카오페이 FE개발팀의 팀장을 맡고 있는 에릭입니다. 사용자 경험과 개발자 경험에 많은 관심을 갖고 있습니다.

태그