0. 구현 배경
위치 추측 게임 서비스, 어데고?!를 제작하며 이미지를 불러오는 경우가 많았다. 이때, 클라이언트에서 사용자가 이미지를 등록하거나, 서버로부터 저장된 이미지를 불러올 때 1-2초 후 이미지가 뜨게 된다.
사용자가 이미지를 보겠다는 동작을 취했음에도 불구하고, 서비스 상에서 동작에 대한 결과를 즉각적으로 보여주지 않아 불친절하고 불편하다는 느낌을 받았다.
하지만 서비스 상에선 사용자의 액션 이후, 내부적으로 코드가 돌아가고 있기에 무언가를 진행하고 있는 것은 사실이다. 즉, 이러한 내부적인 기능이 동작하고 있음을 사용자가 시각적으로 확인할 수 있는 수단이 필요했다. 이미지와 텍스트가 로드되고 있다는 사실을 알려주는 방식은 서비스에서 제공하는 기본 섬네일을 제공하거나 로딩 스피너를 제공하거나, Skeleton(스켈레톤) UI를 제시하는 방법 등이 있다. 이 중 어떠한 요소가 준비 중인지 나타내는 직관적인 방식인 Skeleton UI를 구현하기로 결정했다.
1. Skeleton 적용 적절성 분석
우선 Skeleton 애니메이션을 적용하는 것이 적절한지에 대해 분석했다. 사용하고자 하는 기능의 로드 시간이 1초 미만이라면 Skeleton 화면 자체가 애니메이션 진행을 보여주는 것이 아니라, 잠깐 나타났다가 곧바로 사라지게 되는 화면이 구현되어 오히려 서비스가 불안정하다는 인상을 제시할 수 있기 때문이다.
현재 내가 사용하고자 하는 부분은 다음과 같다.
- 서버에서 이미지와 텍스트를 불러오는 것
- 미리보기 이미지를 등록하는 것
모두 1초에서 길면 2초까지 걸리는 작업이기에 Skeleton 화면을 적용하는 것이 적절하다고 분석을 했다.
⚡ 참고한 블로그: 무조건 스켈레톤 화면을 보여주는 것이 사용자 경험에 도움이 될까요?
2. Skeleton UI 구현
(1) Skeleton 컴포넌트(Skeleton.tsx)
- props: width, height를 전달받아 다양한 크기로 구현이 가능하도록 설정했다.
import { SkeletonWrapper, Shimmer } from './Skeleton.styles';
interface ImageSkeletonProps {
width: number;
height: number;
}
const Skeleton = ({ width, height }: ImageSkeletonProps) => {
return (
<SkeletonWrapper width={width} height={height}>
<Shimmer />
</SkeletonWrapper>
);
};
export default Skeleton;
(2) Skeleton 컴포넌트 스타일(Skeleton.styles.ts)
- 배경 부분
export const SkeletonWrapper = styled.div<{ width: number; height: number }>`
width: ${({ width }) => width}px;
height: ${({ height }) => height}px;
border-radius: 4px;
background: rgba(235, 235, 235, 1);
overflow: hidden;
`;
- Shimmer 부분: 애니메이션을 담당하며, 하이라이트 된 부분이 움직이는 역할이다.
const loading = keyframes`
0%{
transform: translateX(-100%);
}
100%{
transform: translateX(200%);
}
`;
export const Shimmer = styled.div`
width: 50%;
height: 100%;
background-color: rgba(245, 245, 245, 0.6);
box-shadow:
0 0 50px 30px rgba(245, 245, 245, 0.3),
0 0 20px 10px rgba(245, 245, 245, 0.2),
0 0 10px 5px rgba(245, 245, 245, 0.1);
filter: blur(4px);
animation: ${loading} 1.5s infinite ease-in-out;
`;
3. Skeleton UI Storybook 구현
(1) 기본 Storybook
- Image: 이미지의 Skeleton UI 부분이다.
- Text: 텍스트의 Skeleton UI 부분이다.
import LocationListSkeleton from '@/components/Common/Skeleton/LocationListSkeleton';
import Skeleton from '@/components/Common/Skeleton/Skeleton';
import { Meta, StoryObj } from '@storybook/react';
const meta = {
title: 'Common/Skeleton/Basic',
component: Skeleton,
subcomponents: { LocationListSkeleton },
tags: ['autodocs'],
parameters: {
layout: 'centered',
},
} satisfies Meta<typeof Skeleton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Image: Story = {
args: {
width: 60,
height: 60,
},
};
export const Text: Story = {
args: {
width: 202,
height: 21,
},
};
(2) 활용 Storybook
- LocationListSkeleton: Image, Text를 합친 올린 장소 컴포넌트의 Skeleton
4. Skeleton UI 화면에 적용
장소 등록하기 화면에서 각 장소에 대한 이미지를 업로드할 때, 미리 보기 이미지를 제공하게 된다. 이때, 미리 보기 이미지를 모두 사용자에게 시각적으로 제공하기까지 약 2초의 시간이 소요된다. 해당 과정은 다음과 같이 진행된다.
- 사용자가 다중 이미지를 등록한다.
- 시스템 내부에서 다중 이미지의 조건을 확인한다.
- 시스템 내부에서 메타 데이터 중 위경도를 추출하여 저장한다.
- 시스템 내부에서 다중 이미지를 Zustand의 Store에 저장한다.
- 이미지 미리보기 컴포넌트에서 Store의 변경을 확인한 후, 미리 보기 이미지를 렌더링 한다.
현재 사용자가 1단계를 진행한 후, 5단계가 도달하기 전까지 제공되는 상호작용 요소가 없기에, 이 중간 과정을 Skeleton을 제공하여 기능의 진행도를 보여주고자 했다.
(1) 이미지 미리 보기 컴포넌트에 Skeleton 적용
isPreviewLoading[index].map((_, index) => (
<PreviewImage key={index}>
<Skeleton width={60} height={60} />
</PreviewImage>
))
(2) 실제 적용 화면
5. Skeleton UI 구현, Storybook 구현, 화면 적용 PR
⚡주소: https://github.com/urdego/Urdego_Frontend/pull/51
Feat/#50: Skeleton UI 구현 by minjeongss · Pull Request #51 · urdego/Urdego_Frontend
#️⃣ 연관된 이슈 #50 📝 작업 내용 Skeleton UI를 구현했습니다. Skeleton UI의 Storybook을 구현했습니다. 장소 등록하기에서 미리 보기 이미지에 Skeleton UI를 적용했습니다. 📸 스크린샷 Skeleton UI Storybo
github.com
'프로그래밍 - 활용 > Front-end' 카테고리의 다른 글
package.json 분석기: dependencies VS peerDependencies VS devDependencies (2) | 2025.01.13 |
---|---|
프로젝트 자동화 구현하기: Projects 연결, branch 삭제, Organization Vercel 배포편 (2) | 2025.01.09 |
[TypeScript] TypeScript 환경설정 (0) | 2024.05.11 |
[React] axios로 백엔드 연동하기 (4) | 2023.11.19 |