[React/TS] 스켈레톤 코드 (Skeleton)

2022. 12. 2. 16:38·🍞 FrontEnd/React

스켈레톤(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초 동안 스켈레톤이 보이는 것을 확인할 수 있다.

 

저작자표시 (새창열림)

'🍞 FrontEnd > 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
'🍞 FrontEnd/React' 카테고리의 다른 글
  • [React/Node.js] CORS 이슈, Proxy 설정
  • [React] React Suspense이란?
  • [React] Swiper 적용하기 버전 8.0.0
  • [React] JWT를 이용한 로그인 흐름
박빵이
박빵이
2025년에도 갓생살기
  • 박빵이
    기억보다 기록
    박빵이
  • 전체
    오늘
    어제
    • 분류 전체보기 (337)
      • 🍞 FrontEnd (97)
        • HTML+CSS (4)
        • JavaScript (17)
        • TypeScript (4)
        • React (52)
        • Next.js (2)
        • Android (15)
      • 🍞 BackEnd (24)
        • Java (15)
        • Node.js (6)
        • Spring (1)
      • 🍞 Cloud & Infra (0)
        • AWS SAA (0)
        • Microsoft Azure (0)
      • 🍞 Algorithm (147)
        • C++ (4)
        • Baekjoon (41)
        • Programmers (97)
      • 🍞 Computer Science (18)
        • 운영체제 (1)
        • 데이터 통신 (6)
        • 네트워크 (6)
        • 데이터베이스 (1)
      • 🍞 대외활동 & 부트캠프 (42)
        • 삼성 청년 SW 아카데미 (1)
        • LG유플러스 유레카 (0)
        • 한국대학생IT경영학회 (1)
        • IT연합동아리 UMC (17)
        • 길벗 블로깅 멘토 (18)
        • IT연합동아리 피로그래밍 (3)
        • 개발 컨퍼런스 (2)
  • 블로그 메뉴

    • Admin
  • 링크

    • GitHub
  • 인기 글

  • 태그

    map
    안드로이드
    level1
    Android
    길벗 블로깅 멘토링
    유니온파인드
    Java
    react
    Front
    JavaScript
    알고리즘
    C++
    프로그래머스
    코딩자율학습
    길벗 블로깅 멘토
    백준
    위상정렬
    level2
    umc
    코틀린
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
박빵이
[React/TS] 스켈레톤 코드 (Skeleton)
상단으로

티스토리툴바