본문으로 바로가기
윤창원uiwwsw · 작은 우주

개발과 기술

빈 팝업을 줄이는 이미지 로딩 상태 설계

2026-09-10: 설명과 코드 예시를 보완했습니다. 아래 예시는 글의 설계 의도를 전달하기 위한 것이며, 원 프로젝트에 반영된 변경 내역과는 구분합니다.

빈 팝업을 줄이는 이미지 로딩 상태 설계 · 사진 1
빈 팝업을 줄이는 이미지 로딩 상태 설계 · 사진 1 원본 크게 보기 ↗

팝업의 틀은 열렸는데 이미지가 아직 준비되지 않으면 사용자는 빈 화면을 보게 된다. 캐시가 있는 개발 환경에서는 잘 드러나지 않아도 느린 연결에서는 쉽게 확인할 수 있는 문제다.

이때 먼저 구분할 것은 콘텐츠를 마운트하는 시점과 보여주는 시점이다.

마운트하지 않으면 load도 기다릴 수 없다

loaded가 false일 때 null을 반환하고, 자식 이미지의 load 이벤트로 loaded를 true로 만들려 하면 순환이 생긴다. 이미지 자체가 DOM에 없으니 이벤트도 받을 수 없다.

여기서는 팝업의 틀을 열고 로딩 상태를 보여주되, 이미지는 먼저 마운트하는 방식을 사용한다. 여러 이미지의 완료 여부와 실패를 함께 확인한다.

콘텐츠 준비 상태를 다루는 예시

import { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';

export function ImageReadyContent({ children }: { children: ReactNode }) {
  const root = useRef<HTMLDivElement>(null);
  const [state, setState] = useState({ settled: false, failed: false });

  const check = useCallback(() => {
    const images = Array.from(root.current?.querySelectorAll('img') ?? []);
    setState({
      settled: images.every(image => image.complete),
      failed: images.some(image => image.complete && image.naturalWidth === 0),
    });
  }, []);

  useEffect(check, [check, children]);

  return (
    <section aria-busy={!state.settled}>
      {!state.settled && <p role="status">이미지를 불러오고 있습니다.</p>}
      {state.settled && state.failed && <p role="status">일부 이미지를 불러오지 못했습니다.</p>}
      <div
        ref={root}
        onLoadCapture={check}
        onErrorCapture={check}
        style={{ visibility: state.settled ? 'visible' : 'hidden' }}
      >
        {children}
      </div>
    </section>
  );
}

onLoadCapture는 자식 이미지에서 발생한 이벤트를 상위 요소에서 확인하는 데 쓴다. 이벤트 하나가 왔다고 모든 이미지가 준비된 것은 아니므로 각각의 상태를 다시 읽는다.

캐시된 이미지도 고려하기 위해 마운트 후 complete를 확인한다. complete는 로드 실패에서도 true일 수 있어 naturalWidth를 함께 확인한다. MDN 이미지 complete

<ImageReadyContent key={bannerUrl}>
  <img src={bannerUrl} alt="이벤트 안내" width={640} height={320} loading="eager" />
</ImageReadyContent>

예시의 범위

이미지 집합이 바뀌면 key를 바꿔 준비 상태를 초기화한다. 이 코드는 마운트된 DOM의 일반 img를 대상으로 하며 배경 이미지, 폰트, Shadow DOM이나 iframe 안의 리소스까지 추적하지 않는다. 자식 컴포넌트가 나중에 이미지를 추가하는 구조라면 별도의 준비 상태 계약이 필요하다.

숨겨진 상태에서 로딩을 기다리는 이미지에는 지연 로딩을 섞지 않는 편이 단순하다. 완료되지 않는 요청을 위한 시간 제한과 재시도 정책은 제품에서 추가로 정해야 한다.

실제 Dialog가 닫힌 동안 자식을 제거한다면, 열기 전에 준비하려는 이미지는 별도의 preload 계층에서 다뤄야 한다. Dialog의 포커스 관리와 닫기 동작 역시 이 콘텐츠 예시와 별개다.

빈 화면을 줄이는 데 중요한 것은 이벤트 이름 하나보다, 마운트·로딩·실패·노출의 순서를 서로 모순되지 않게 만드는 일이다.

Assisted by AI

윤창원이 벨로그에 남긴 글을 이 작은 우주에도 모았습니다. 사진은 누르면 원본 크기로 볼 수 있습니다. 원문의 전체 서식 보기 ↗

모든 글 둘러보기 →