스켈레톤(Skeleton) 이란?
웹 페이지에서 로드 시간이 짧은 것처럼 보이게 하는 몇 가지 방법들이 있다. 스켈레톤은 그 방법 중에 하나로서 데이터가 로드되기 전에 콘텐츠의 자리비움(placholder)를 표시해서 사용자가 기다리는 시간을 좀 덜 지루하게 느끼게끔 하는 UI다.
그래서 오늘은 프로젝트를 로딩할 때 몇 초 동안 화면이 비어있는 것보단 스켈레톤을 보여주도록 설정해보려고 한다.
App.tsx
loading 상태를 생성하고 useEffect와 setTimeout을 이용해 loading이 2초 동안 되게 구현했다.
loading 중이면 Placeholder 즉, 스켈레톤 컴포넌트를 렌더링하고
loading 끝나면 Item 컴포넌트를 렌더링한다.
import React, { useEffect, useState } from "react";
import styled from "@emotion/styled/macro";
import Skeleton from "./components/Skeleton";
const Base = styled.div`
display: grid;
width: 100%;
grid-template-columns: repeat(5, 1fr);
column-gap: 12px;
row-gap: 24px;
`;
const Container = styled.div`
display: flex;
flex-direction: column;
box-shadow: rgb(0 0 0 / 4%) 0px 4px 16px 0px;
border-radius: 4px;
padding: 1rem;
`;
const ImageWrapper = styled.div`
width: 320px;
height: 220px;
`;
const Image = styled.img`
width: 100%;
height: 100%;
object-fit: cover;
`;
const Info = styled.div`
display: flex;
flex-direction: column;
flex: 1 1 0%;
`;
const Title = styled.h4`
margin: 8px 0 0 0;
padding: 0;
font-size: 24px;
`;
const Description = styled.p`
margin: 8px 0 0 0;
padding: 0;
font-size: 16px;
`;
const Placeholder: React.FC = () => (
<Container>
<ImageWrapper>
<Skeleton width={320} height={220} />
</ImageWrapper>
<Info>
<div style={{ height: "8px" }}></div>
<Skeleton width={150} height={29} rounded />
<div style={{ height: "8px" }}></div>
<Skeleton width={200} height={19} rounded />
</Info>
</Container>
);
const Item: React.FC = () => {
return (
<Container>
<ImageWrapper>
<Image src="https://i.ibb.co/pWDL42M/react.png" />
</ImageWrapper>
<Info>
<Title>안녕하세요</Title>
<Description>박브레드</Description>
</Info>
</Container>
);
};
function App() {
const [loading, setLoading] = useState(true);
useEffect(() => {
setTimeout(() => {
setLoading(false);
}, 2000);
}, []);
return (
<Base>
{loading
? Array.from({ length: 25 }).map((_, idx) => <Placeholder key={idx} />)
: Array.from({ length: 25 }).map((_, idx) => <Item key={idx} />)}
</Base>
);
}
export default App;
Skeleton.tsx
import React, { useMemo } from "react";
import styled from "@emotion/styled/macro";
import { keyframes, css } from "@emotion/react";
interface Props {
width?: number;
height?: number;
circle?: boolean;
rounded?: boolean;
count?: number;
unit?: string; // px, em, rem, %
animation?: boolean;
color?: string;
style?: React.CSSProperties;
}
const pulseKeyframe = keyframes`
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
`;
const pulseAnimation = css`
animation: ${pulseKeyframe} 1.5s ease-in-out infinite;
`;
const Base = styled.div<Props>`
${({ color }) => color && `background: ${color}`};
${({ rounded }) => rounded && `border-radius: 8px`};
${({ circle }) => circle && `border-radius: 50%`};
${({ width, height }) => (width || height) && "display: block"};
${({ animation }) => animation && pulseAnimation};
width: ${({ width, unit }) => width && unit && `${width}${unit}`};
height: ${({ height, unit }) => height && unit && `${height}${unit}`};
`;
const Content = styled.span`
opacity: 0;
`;
const Skeleton: React.FC<Props> = ({
style,
rounded,
circle,
width,
height,
animation = true,
unit = "px",
color = "#F4F4F4",
count,
}) => {
const content = useMemo(
() => [...Array({ length: count })].map(() => "-").join(""),
[count]
);
return (
<Base
style={style}
rounded={rounded}
circle={circle}
width={width}
height={height}
animation={animation}
unit={unit}
color={color}
>
<Content>{content}</Content>
</Base>
);
};
export default Skeleton;
✅ 스켈레톤 결과
새로고침을 할 때마다, 즉 페이지가 새로 렌더링 될 때마다 2초 동안 스켈레톤이 보이는 것을 확인할 수 있다.
'🍞 Front-End > React' 카테고리의 다른 글
[React/Node.js] CORS 이슈, Proxy 설정 (0) | 2022.12.19 |
---|---|
[React] React Suspense이란? (0) | 2022.12.02 |
[React] Swiper 적용하기 버전 8.0.0 (0) | 2022.11.29 |
[React] JWT를 이용한 로그인 흐름 (0) | 2022.11.26 |
[React] JWT 토큰 : 웹 저장소별 차이 (0) | 2022.11.26 |