
유튜브나 페이스북을 사용하다 보면 동영상, 게시글 등을 로딩할 때 위와 같은 UI를 흔히 볼 수 있습니다. 데이터를 fetching/loading 할 때 loading spinner나 bar 대신 디스플레이 될 컨텐츠의 전반적인 구조(placeholder)를 디스플레이 시키는 방법 Skeleton Screen입니다.
Skeleton Screen은 사용자가 기다리는 동안 진행 상황을 체감 하며 몰입감을 유지하는 것을 목적으로, 아래와 같은 UX를 개선하는 장점/효과를 갖고 있습니다.
출처: https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a
위 아티클에서는 skeleton에 어떤 애니메이션을 적용시켜야 유저들의 delay 체감을 축소 시킬 수 있는지에 대한 UX 케이스 스터디를 진행했습니다.
스터디 결과를 요약하자면,
- 애니메이션이 존재하고,(정적 보단 동적인 UI)
- Wave animation을 사용하고,(paulsing animation 보다 효과가 있음)
- Wave animation 속도는 느려야하며,
- 왼쪽에서 오른쪽 방향으로 적용되는 Wave animation이어야 한다.
1에서 언급한 아티클에 포함된 내용입니다.
현재 대부분의 Skeleton Screen 구현은 단순히 Splash Screen 역할만 담당하고 있는데, 더 나은 로딩 UX 개선을 위해선 더욱이 발전시켜 나가야 하는 분야로 각광받고 있습니다.
Luke Wroblewski가 2018년 Google Conversion 행사에서 강조한 개념으로, Skeleton Screen은 텍스트와 이미지 같은 실제 콘텐츠로 빠르게 대체되어야 한다는 전망을 언급하기도 했습니다.
참고로 상위 아티클은 2018년에 작성 되어 연식이 좀 있습니다만,
https://medium.com/javascript-in-plain-english/what-are-effective-skeleton-screens-56352f775c4e
2023년에 발행된 다른 아티클을 찾아보면 skeleton scereen은 꾸준히 선호되는 기술이라 추정 됩니다.
Material UI?
- 구글의 Marterial Design 가이드 라인에 기반한 오픈소스 리액트 컴포넌트 라이브러리
- UI개발에 필요한 다양한 컴포넌트와 디자인 템플릿 제공
- 리액트와 호환성이 높다
https://mui.com/material-ui/react-skeleton/
해당 라이브러리에서 제공하는 Skeleton 컴포넌트를 활용해서 사용할 수 있습니다. varient, animation으로 제공되는 UI와 animation을 끌어다 와서 사용 할 수 있기 때문에 편리해보입니다.
📍주의
Material UI는 전반적으로 컴포넌트들이 무겁고, 안드로이드 최적화 되어있기 때문에 커스터마이징에 한계가 있습니다. 이미 Material UI를 활용한 프로젝트를 진행하고 있다면 상관 없겠지만, 별도의 css를 사용하여 customizing한 ui를 구상 중이라면, 다른 방안을 모색하는걸 추천합니다.
1. paulse animation이 적용 되었을 시

1. varient: 기본으로 text 적용
2. animation: 기본으로 paulse 적용, wave로 설정 시 변경됨
{/* For variant="text", adjust the height via font-size */}
<Skeleton variant="text" sx={{ fontSize: '1rem' }} />
{/* For other variants, adjust the size with `width` and `height` */}
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={210} height={60} />
<Skeleton variant="rounded" width={210} height={60} />
2. 애니메이션 별 효과

//기본적으로 text placeholder 적용됨
<Skeleton /> //paulse 애니메이션 적용
<Skeleton animation="wave" /> //wave 애니메이션 적용
<Skeleton animation={false} /> //애니메이션 적용X
https://tailwindcss.com/docs/animation#pulse
animate-puls
https://tailwindcss.com/docs/animation#pulse
테일윈드에서 기본으로 제공하는 에니메이션은 pulse(위 아티클에서는 pulse대신 wave 사용이 좋은 결과가 있었음)이 있습니다.
placeholder를 css로 구현한 뒤 부모 클래스에 애니메이션을 입력하는 방법이 있습니다.

<div class="border border-blue-300 shadow rounded-md p-4 max-w-sm w-full mx-auto">
<div class="animate-pulse flex space-x-4">
<div class="rounded-full bg-slate-700 h-10 w-10"></div>
<div class="flex-1 space-y-6 py-1">
<div class="h-2 bg-slate-700 rounded"></div>
<div class="space-y-3">
<div class="grid grid-cols-3 gap-4">
<div class="h-2 bg-slate-700 rounded col-span-2"></div>
<div class="h-2 bg-slate-700 rounded col-span-1"></div>
</div>
<div class="h-2 bg-slate-700 rounded"></div>
</div>
</div>
</div>
</div>
테일윈드의 장점, 커스터마이징이 자유롭다!
...를 이용해서 wave animation을 적용해 보았습니다.
extend: {
animation: {
wave: "wave 2s linear infinite"
},
keyframes: {
wave: {
"0%": { backgroundPosition: "200% 0" },
"100%": { backgroundPosition: "-200% 0" }
}
},
backgroundImage: {
"wave-gradient":
"linear-gradient(90deg, #e0e0e0 25%, #f0f0f0 50%, #e0e0e0 75%)"
}
}
백그라운드 이동으로 animation, keyframe을 설정했기 때문에 모든 태그마다 해당 애니메이션을 추가해야한다는 고생스러움 + 애니메이션 디스플레이가 상이함이 있었습니다. 다른 방법은 없는지 좀 더 탐구해봐야겠네요.
export default function Test() {
return (
<div className="border border-gray-300 shadow rounded-md p-4 max-w-sm w-full mx-auto">
<div className=" flex space-x-4">
<div className="animate-wave bg-wave-gradient bg-[length:200%_100%] rounded-full bg-gray-300 h-10 w-10"></div>
<div className="flex-1 space-y-6 py-1">
<div className="animate-wave bg-wave-gradient bg-[length:200%_100%] h-2 bg-gray-300 rounded"></div>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-4">
<div className="animate-wave bg-wave-gradient bg-[length:200%_100%] h-2 bg-gray-300 rounded col-span-2"></div>
<div className="animate-wave bg-wave-gradient bg-[length:200%_100%] h-2 bg-gray-300 rounded col-span-1"></div>
</div>
<div className="animate-wave bg-wave-gradient bg-[length:200%_100%] h-2 bg-gray-300 rounded"></div>
</div>
</div>
</div>
</div>
);
}

언뜻 보면 낫뱃이지만, 앞에서 언급한 바와 같이 마음에 들진 않습니다. 더 나은 방법을 찾아내면 다시 글을 쓰도록 하겠습니다ㅎㅎ
그동안 isLoading의 상태일 때 로딩 스피너를 주로 활용했습니다만, UX를 최적화 시킬 수 있는 방법을 찾아낸 것 같습니다.
아직 서비스를 시작하지 않은 프로덕트들에 도입하는 방법을 모색해 봐야겠습니다.
작은 디테일에서 UI/UX에 대한 큰 개선 사항이 발생하고, 이는 결국 프로덕트의 수준을 높인다고 생각하기 때문에 (prefetching 등 실제 fetching 속도를 높이는 것과 같이) 프론트엔드 개발을 하면 어쩔 수 없이 UI/UX에 대한 이해와 공부도 요구되는 것 같습니다.
그래도 오랜만에하는 UI/UX 공부는 재미있네요 하핫!
https://medium.com/javascript-in-plain-english/what-are-effective-skeleton-screens-56352f775c4e
https://mui.com/material-ui/react-skeleton/
https://uxdesign.cc/what-you-should-know-about-skeleton-screens-a820c45a571a
https://www.uxdesigninstitute.com/blog/whats-a-skeleton-screen/
https://delba.dev/blog/animated-loading-skeletons-with-tailwind