1. Skeleton UI란?
Skeleton UI는 콘텐츠가 로드되기 전에 표시되는 임시 화면으로, 실제 콘텐츠의 레이아웃을 미리 보여주는 플레이스홀더입니다.
주로 회색 막대나 박스 형태로 표시되며, 로딩 중임을 나타내는 애니메이션 효과를 포함하는 것이 일반적입니다.
2. Skeleton UI를 사용해야 하는 이유
인지된 성능 향상
- 사용자는 빈 화면이나 로딩 스피너보다 Skeleton UI를 볼 때 로딩 시간이 더 짧게 느껴진다.
- 콘텐츠의 구조를 미리 보여줄 수 있다.
더 나은 사용자 경험
- 갑작스러운 레이아웃 변경을 방지하여 CLS(Cumulative Layout Shift)를 줄일 수 있다.
- 앱이 더 반응적이고 생동감 있게 느껴진다.
3. SkeletonUI 적용하는 법
컴포넌트 이름은 BoardPreview(BoardCard4개), BoardCard, 스타일 파일은 board-card.css.ts 입니다.
저같은 경우는 기존 board-card스타일을 활용하여 만들거기 때문에, board-card.css에 필요한 스타일만 추가해줬습니다 :)
그리고 Skeleton은 기존의 BoardCard컴포넌트에서 html부분만 똑같이 만들었습니다.
board-card.css.ts 추가 부분
//애니메이션 정의도 같은 파일에 추가
const pulse = keyframes({
'0%': { opacity: 1 },
'50%': { opacity: 0.5 },
'100%': { opacity: 1 },
});
export const skeleton = style({
background: vars.color.g200,
color: vars.color.g200,
animation: `${pulse} 1.5s ease-in-out infinite`,
});
애니메이션은 선택, 배경색과 글자색 선택해서 skeleton style을 따로 생성해주면됩니다.
BoardPreviewSkeleton.tsx
const BoardPreviewSkeleton = () => {
return (
<div className={boardContainer}>
{[...Array(4)].map((_, index) => (
<div className={styles.containerSkeleton}>
<div className={`${styles.imageBox}`}>
<div className={`${styles.image} ${styles.skeleton}`}></div>
</div>
<div className={`${styles.contentContainer}`}>
<div
className={`${styles.languageSkeleton} ${styles.skeleton}`}
></div>
<span className={`${styles.title} ${styles.skeleton}`}></span>
<div className={`${styles.infoContainer} ${styles.skeleton}`}>
-
</div>
<div className={`${styles.infoContainer} ${styles.skeleton}`}>
-
</div>
<div className={`${styles.profileContainer}`}>
<div
className={`${styles.profileSkeleton} ${styles.skeleton}`}
></div>
<span
className={`${styles.profileInfoSkeleton} ${styles.skeleton}`}
>
f
</span>
</div>
</div>
</div>
))}
</div>
);
};
className 잘보시면 title, infoContainer같은 것은 기존 컴포넌트에서 사용한 스타일입니다.
기존 스타일에 skeleton 클래스이름만 추가하여 적용한 것을 볼 수 있습니다.
BoardPreview.tsx 수정
const [isLoading, setIsLoading] = useState<boolean>(true);
const getBoardPreviewList = async () => {
if (country) {
const data = await getBoardPreview(country);
setBoardPreview(data.data);
setIsLoading(false);
}
};
return (
{isLoading ? (
<BoardPreviewSkeleton />
) : (
<ul className={styles.boardContainer}>
{boardPreview.map((board) => (
<li key={board.boardId}>
<BoardCard props={board} />
</li>
))}
</ul>
)
}
)
저는 isLoading변수 설정해서 보여주는 방식으로 했습니다!
처음에는 true로 초기화하고, 데이터를 불러와서 적용하면 isLoading을 false로 바꾸는 방식으로 진행했습니다.
4. 결과