[개발지식공유] Skeleton UI

이나현·2022년 10월 27일
0

오라운드

목록 보기
5/18
post-thumbnail
  • 데이터를 로딩하기 전, 로딩화면에 뿌려줄 수 있는 건 스피너, 프로그레스바, 스켈레톤 ui 등이 있다.
  • 데이터가 어떤 형식으로 뿌려지는 지 보여주기 위해, 스켈레톤 ui를 사용했다.
  • 스켈레톤 ui는 Material Ui의 스켈레톤을 사용했다.
    • 이유
      • material ui를 이미 서비스에서 사용했기 때문
      • 보이는 뷰가 원하는 디자인이기 때문
  1. 구현 원리

(1) 이미지가 로딩되었는지를 state로 저장한다.

(2) 로드가 완료되었을 때, state를 true로 바꾼다.

(3) 로딩state가 false면 skeleton이 뜨게 되고, 로딩 state가 true면 이미지를 보여준다.

  1. 직면한 문제
// 이미지 컴포넌트
useEffect(() => {
	window.addEventListner('load', function(){
	const image = document.querySelector('img');
  const isLoaded = image.complete && image.naturalHeight !== 0;
    useState(isLoaded);
})
},[] )

위의 코드로 image가 로드 완료되었을 때를 판별하려고 했으나, window 전역에서 img 태그를 확인하기 때문에 해당 img가 내가 원하는 이미지로 들어오는지는 미지수였다.

그리고 페이지별로 데이터 패칭이 csr과 ssr로 다르게 불리기 때문에, ssr의 경우에는 onload가 작동되지 않고 src가 뿌려지는 문제가 있었다.

따라서, 이렇게 window로 하기보다 classname을 지정해서 특정 class를 잡으려고 했으나 react 기반의 nextjs를 사용하는 우리 서비스에서는 useRef라는 기능이 더 적합한 것 같아 useRef를 사용했다.

  1. useRef를 사용해 image태그가 로드 완료되었는지 확인
useEffect(() =>{
    if (imageRef.current && imageRef.current?.complete) {
      setCompleteLoading(true);
    }
  }, []);

이렇게 하면 image태그가 useRef로 들어오게 된다.

처음 렌더링 때 바뀐 state 값으로 style display block 와 none을 줘서 skeleton과 이미지를 상태값에 따라 변화되어 보이게 만들었다.

import React, { useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import SkeletonBox from '@src/components/common/animation/SkeletonBox';

interface ImageWrapperProps {
  completeLoading?: boolean;
}

const ImageWrapper = styled.div<ImageWrapperProps>`
  display: ${({ completeLoading }) => (completeLoading ? 'block' : 'none')};
  width: 100%;
  height: 100%;
`;

const NoImageWraaper = styled.div<ImageWrapperProps>`
  display: ${({ completeLoading }) => (completeLoading ? 'none' : 'block')};
  width: 100%;
  height: 100%;
`;

const Image = (props: ImageProps) => {
  const { src, width, height, borderRadius, ...rest } = props;
  const [completeLoading, setCompleteLoading] = useState(false);
  const imageRef = useRef();

  useEffect(() =>{
    if (imageRef.current && imageRef.current?.complete) {
      setCompleteLoading(true);
    }
  }, []);

  const handleImgError = (e: any) => {
    e.target.src = '~~~';
    e.target.onerror = null;
  };

  return (
    <>
      <NoImageWraaper
        completeLoading={completeLoading}
      >
        <SkeletonBox/>
      </NoImageWraaper>
      <ImageWrapper
        completeLoading={completeLoading}
      >
        <StyledImage
          ref={imageRef}
          width={width}
          height={height}
          src={src}
          borderRadius={borderRadius}
          onError={handleImgError}
          onLoad={() => {
            setCompleteLoading(true);
          }}
        />
      </ImageWrapper>
    </>
  );
};

export default Image;
💡 확실히, skeleton ui는 화면에 따라 각각 스타일을 잡아줘야하는게 단점으로 꼽히는데, 해당 image컴포넌트를 공동으로 사용하려고 해도, 각각 style이 다르기 때문에 컴포넌트 별로 잘 사용해야했던게 불편했다.
profile
technology blog

0개의 댓글