GODRICH
#css애니메이션#버튼#로딩#전환효과#스크롤#대시보드#실전ui#디자인트렌드

이미지 지연 로딩 9종 — 레이지 로딩 자리표시자 모음

이미지 지연 로딩 아홉 가지 가운데 하나는 스크롤 위치 값으로 교차 비율을 계산해 이미지를 페이드인시키고, 하나는 클릭 여부 값으로 로드 시점을 계산해 지도 iframe 자리를 실제 화면으로 바꾸며, 하나는 유휴 시간 값으로 지연 시간을 계산해 무거운 위젯을 뒤늦게 불러옵니다.

아홉 개 모두 실제 이미지·영상·iframe 대신 자리표시자를 먼저 그려 두고, 어떤 값이 조건을 넘는 순간에만 그 자리를 실제 콘텐츠로 바꾸는 구조를 공유합니다. 아래 데모는 실 네트워크 요청을 전혀 만들지 않습니다 — 이미지·영상·지도는 모두 CSS 로 그린 자리표시자이고, 실제 프로젝트에 옮길 때 어디를 <img loading="lazy">·fetch·진짜 iframe URL 로 바꿔야 하는지는 아래에서 항목마다 짚습니다.

자동 재생 · 눌러서 해당 항목으로 · 전부 한 zip

타이머·관찰자·유휴 콜백 — 아홉 개가 로드 시점을 판정하는 세 가지 방식

아홉 개를 뜯어보면 로드 시점을 판정하는 값의 출처가 셋으로 갈립니다. 01·04·05 는 setTimeout 으로 미리 정해 둔 지연 시간이 지났는지를 재고, 03·08 은 스크롤·교차 값을 직접 관찰해 임계치를 넘었는지를 재며, 09 는 브라우저가 알려주는 유휴 남은시간을 누적해 예산에 닿았는지를 잽니다. 06 은 재생 시작 여부를, 02 는 화면 너비 값을 판정 기준으로 삼고, 07 은 클릭 지점을 기준으로 삼습니다.

화면을 매 프레임 다시 그려야 하는 쪽(02·06·07·08)만 requestAnimationFrame 을 쓰고, 한 번의 상태 전환으로 끝나는 쪽(01·04·05·09)은 setTimeout·requestIdleCallback 으로도 충분합니다. 입력 방식도 갈립니다 — 01·02·05·07 은 클릭과 키보드(Enter/Space)를 함께 듣고, 03·08 은 스크롤·휠·터치 시작을 함께 들어 실제 스크롤 조작과 데모용 버튼 조작을 둘 다 받아들이며, 04·06·09 는 클릭 하나만 듣습니다.

코드는 아홉 조각, 계산은 한 겹씩 들어 있습니다

아래 항목마다 실제 파일에서 그대로 뽑은 코드 조각과 값이 어디로 흘러가는지를 붙였습니다. 값 하나가 스타일을 다시 계산하는 지점만 골라 실었으므로, 항목 전체 코드는 zip 안 파일에서 확인할 수 있습니다.

01. 대체 서체에서 실제 폰트로 자연스럽게 넘어가는 헤드라인

대체 서체에서 실제 폰트로 넘어가는 지연 로딩 텍스트
새 창에서 열기

01 · 대체 서체에서 실제 폰트로 넘어가는 지연 로딩 텍스트 — 측정 누적면적 5.237% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STAGGER_STEP_MSFOUT_DELAY_MSRESET_DELAY_MS

글자마다 인덱스 값에 STAGGER_STEP_MS 를 곱해 CSS transition-delay 로 흘려보내므로, 같은 시각에 폰트가 준비돼도 글자는 왼쪽부터 순서대로 실제 서체 모양으로 바뀝니다. 두 폰트 스택 사이의 자간·굵기 차이가 큰 화면일수록 이 전환이 도드라집니다.

  // 계산 겹: 글자 인덱스 값으로 전환 지연 시간을 계산해 순서대로 스프링 오버슛이 번진다.
  var chars = [];
  for (var i = 0; i < TEXT.length; i += 1) {
    var span = document.createElement('span');
    span.className = 'lff__char';
    span.textContent = TEXT[i] === ' ' ? ' ' : TEXT[i];
    span.style.transitionDelay = (i * STAGGER_STEP_MS) + 'ms';
    head.appendChild(span);
    chars.push(span);
  }

  function resetState() {
    root.classList.remove('lff--loaded');
    for (var i = 0; i < chars.length; i += 1) { chars[i].classList.remove('lff__char--loaded'); }
    badge.textContent = '대체 서체 표시 중';
  }

  function swapToLoaded() {
    root.classList.add('lff--loaded');
    for (var i = 0; i < chars.length; i += 1) { chars[i].classList.add('lff__char--loaded'); }
    badge.textContent = '실제 서체 로드 완료';
  }

02. 화면 너비 값이 고르는 저해상도·고해상도 배경 전환

화면 너비 값이 고르는 배경 지연 로딩
새 창에서 열기

02 · 화면 너비 값이 고르는 배경 지연 로딩 — 측정 누적면적 38.076% · 움직인 프레임 9/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BREAKPOINT_PXCROSSFADE_MSWIDTH_NARROW

ResizeObserver 가 알려주는 컨테이너 너비 값으로 저해상도·고해상도 배경 중 무엇을 그릴지 고른 뒤, 자리표시자 색에서 실제 배경으로 넘어가는 전환은 진행률을 매 프레임 다시 계산해 블러 반경과 불투명도를 함께 바꿉니다.

  // 계산 겹: 경과 시간 값을 전체 전환 시간 값으로 나눠 진행률을 구하고,
  // 그 진행률로 블러 반경과 불투명도를 매 프레임 다시 계산한다.
  function crossfadeIn(el, duration) {
    var start = performance.now();
    if (raf) { cancelAnimationFrame(raf); }
    function step(now) {
      var progress = Math.min((now - start) / duration, 1);
      el.style.opacity = String(progress);
      el.style.filter = 'blur(' + (6 * (1 - progress)) + 'px)';
      if (progress < 1) { raf = requestAnimationFrame(step); }
    }
    raf = requestAnimationFrame(step);
  }

03. 스크롤 위치 값이 교차 비율을 계산하는 이미지 페이드인

스크롤 위치 값이 교차 비율을 계산하는 이미지 페이드인
새 창에서 열기

03 · 스크롤 위치 값이 교차 비율을 계산하는 이미지 페이드인 — 측정 누적면적 36.04% · 움직인 프레임 23/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

INTERSECTION_THRESHOLDSTEPSapplyRatio

IntersectionObserver 가 넘겨주는 교차 비율 값으로 블러 반경과 스케일을 매 관찰마다 다시 계산해, 뷰포트에 걸친 정도만큼 이미지가 또렷해집니다. 비율이 INTERSECTION_THRESHOLD 를 넘는 순간 로드 완료로 고정됩니다.

  var INTERSECTION_THRESHOLD = 0.4;
  var STEPS = [];
  for (var s = 0; s <= 20; s += 1) { STEPS.push(s / 20); }

  // 계산 겹: 교차 비율 값으로 블러 반경·스케일을 매 관찰마다 다시 계산해
  // 뷰포트에 걸친 정도만큼 이미지가 또렷해진다. 비율이 기준을 넘으면 로드 완료로 고정.
  function applyRatio(card, ratio) {
    var img = card.querySelector('.lfv__img');
    var placeholder = card.querySelector('.lfv__placeholder');
    var blur = 10 * (1 - ratio);
    var scale = 0.92 + 0.08 * ratio;
    img.style.opacity = String(ratio);
    img.style.filter = 'blur(' + blur.toFixed(1) + 'px)';
    img.style.transform = 'scale(' + scale.toFixed(3) + ')';
    placeholder.style.opacity = String(Math.max(0, 1 - ratio * 1.4));
    if (ratio >= INTERSECTION_THRESHOLD) {
      card.classList.add('lfv__card--loaded');
    }
    if (card === cards[Math.min(cards.length - 1, Math.round(lastMaxIndex))]) {
      badge.textContent = '교차 비율 ' + Math.round(ratio * 100) + '%';
    }
  }

04. 이름 값이 이니셜부터 보여주는 아바타

이름 값이 이니셜을 계산하는 아바타 지연 로딩
새 창에서 열기

04 · 이름 값이 이니셜을 계산하는 아바타 지연 로딩 — 측정 누적면적 1.731% · 움직인 프레임 8/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

ROW_STAGGER_MSLOAD_BASE_MSPALETTE

이름 문자열에서 이니셜 두 글자를 자르는 계산과, 이름 글자 코드값을 모두 더한 뒤 팔레트 길이로 나눈 나머지로 배지 색을 고르는 해시 계산이 함께 돌아 프로필 사진이 없는 동안에도 사람마다 다른 배지를 보여줍니다.

  // 계산 겹 1: 이름 문자열에서 이니셜 두 글자를 잘라낸다.
  function getInitials(name) { return name.slice(0, 2); }

  // 계산 겹 2: 이름 글자 코드값을 모두 더한 뒤 팔레트 길이로 나눈 나머지로 배지 색을 고른다(해시).
  function colorHashClass(name) {
    var sum = 0;
    for (var i = 0; i < name.length; i += 1) { sum += name.charCodeAt(i); }
    return PALETTE[sum % PALETTE.length];
  }

  var rows = NAMES.map(function (name, i) {
    var li = document.createElement('li');
    li.className = 'lai__row';
    li.innerHTML =
      '<span class="lai__avatar ' + colorHashClass(name) + '" data-idx="' + i + '">' +
        '<span class="lai__initials">' + getInitials(name) + '</span>' +
        '<span class="lai__photo"></span>' +
      '</span>' +
      '<span class="lai__name">' + name + '</span>';
    list.appendChild(li);
    return li;
  });

05. 격자 위치 값이 순서를 계산하는 갤러리 리빌

격자 위치 값이 순서를 계산하는 갤러리 지연 로딩
새 창에서 열기

05 · 격자 위치 값이 순서를 계산하는 갤러리 지연 로딩 — 측정 누적면적 20.573% · 움직인 프레임 7/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

STAGGER_STEP_MSSTART_DELAY_MSdelayFor

행 값과 열 값을 더한 맨해튼 거리로 지연 시간을 계산하므로 왼쪽 위 칸이 가장 먼저, 오른쪽 아래로 갈수록 늦게 실제 사진으로 바뀝니다. STAGGER_STEP_MS 를 90 에서 160 으로 늘리면 뒤쪽 칸일수록 나타나기까지 시간 차이가 커집니다.

  var cells = [];
  for (var i = 0; i < COLS * ROWS; i += 1) {
    var cell = document.createElement('div');
    cell.className = 'lgs__cell';
    cell.innerHTML = '<div class="lgs__placeholder"></div><div class="lgs__photo lgs__photo--' + (i % 4) + '"></div>';
    grid.appendChild(cell);
    cells.push(cell);
  }

  // 계산 겹: 격자 위치(행 값 + 열 값)로 지연 시간을 계산한다 — 맨해튼 거리 스태거.
  // 왼쪽 위(행0·열0)가 가장 먼저, 오른쪽 아래로 갈수록 늦게 실제 사진으로 바뀐다.
  function delayFor(idx) {
    var row = Math.floor(idx / COLS);
    var col = idx % COLS;
    return START_DELAY_MS + (row + col) * STAGGER_STEP_MS;
  }

06. 재생 값이 판정하는 영상 포스터 전환

재생 값이 판정하는 영상 포스터 지연 로딩
새 창에서 열기

06 · 재생 값이 판정하는 영상 포스터 지연 로딩 — 측정 누적면적 21.261% · 움직인 프레임 15/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

BUFFER_DURATION_MSPLAY_HOLD_MSrunBuffering

경과 시간 값을 버퍼링 전체 시간 값으로 나눠 진행률을 구하고, 그 진행률을 conic-gradient 각도 값으로 매 프레임 다시 써서 링이 돕니다. 진행률이 1 에 닿으면 버퍼링 클래스를 지우고 실제 영상 프레임으로 넘어갑니다.

  // 계산 겹: 경과 시간 값을 버퍼링 전체 시간 값으로 나눠 진행률을 구하고,
  // 그 진행률을 conic-gradient 각도 변수로 매 프레임 다시 써 링이 돈다.
  function runBuffering(onDone) {
    var start = performance.now();
    frame.classList.add('lvp__frame--buffering');
    function step(now) {
      var progress = Math.min((now - start) / BUFFER_DURATION_MS, 1);
      ring.style.background = 'conic-gradient(from -90deg, #5b8cf8 ' + (progress * 360).toFixed(1) + 'deg, rgba(255,255,255,.18) 0)';
      if (progress < 1) {
        raf = requestAnimationFrame(step);
      } else {
        frame.classList.remove('lvp__frame--buffering');
        onDone();
      }
    }
    raf = requestAnimationFrame(step);
  }

07. 클릭 지점 값이 로드 시점을 계산하는 지도 iframe

클릭 값이 로드 시점을 계산하는 iframe 지연 로딩
새 창에서 열기

07 · 클릭 값이 로드 시점을 계산하는 iframe 지연 로딩 — 측정 누적면적 21.261% · 움직인 프레임 10/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

RIPPLE_DURATION_MSLOAD_DELAY_MSmaxDistanceToCorner

클릭한 지점에서 프레임 네 모서리까지의 최대 거리를 피타고라스 계산으로 구해 리플 반지름 목표값으로 삼으므로, 클릭한 자리에서 시작한 원이 프레임 전체를 정확히 덮은 뒤에야 정적 안내가 실제 iframe 자리로 바뀝니다.

  // 계산 겹: 클릭 지점에서 프레임 네 모서리까지의 최대 거리(피타고라스) 를
  // 리플 반지름 목표값으로 계산해, 실제 지점에서 시작하는 원이 프레임을 덮는다.
  function maxDistanceToCorner(x, y, w, h) {
    var dTL = Math.sqrt(x * x + y * y);
    var dTR = Math.sqrt((w - x) * (w - x) + y * y);
    var dBL = Math.sqrt(x * x + (h - y) * (h - y));
    var dBR = Math.sqrt((w - x) * (w - x) + (h - y) * (h - y));
    return Math.max(dTL, dTR, dBL, dBR);
  }

08. 스크롤 임계치 값이 채우는 아래 섹션

스크롤 값이 임계치를 계산하는 아래 섹션 지연 로딩
새 창에서 열기

08 · 스크롤 값이 임계치를 계산하는 아래 섹션 지연 로딩 — 측정 누적면적 16.765% · 움직인 프레임 12/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

SCROLL_THRESHOLD_PXAUTOSCROLL_STEP_PXcheckThreshold

스크롤 컨테이너의 scrollTop 값을 정해 둔 임계치와 비교해 넘어선 순간에만 채운 상태로 바뀌고, 다시 임계치 위로 올라가면 빈 상태로 되돌립니다. 아래로 내려야만 실제로 뭔가 일어나는 화면을 확인할 때 유용한 구조입니다.

  // 계산 겹: 스크롤 컨테이너의 scrollTop 값을 정해 둔 임계치(px)와 비교해,
  // 넘어선 순간에만 filled 상태로 바뀐다(넘기 전엔 빈 자리 유지).
  function checkThreshold() {
    var over = view.scrollTop >= SCROLL_THRESHOLD_PX;
    if (over && !filled) {
      filled = true;
      section.classList.add('lsr__section--filled');
    } else if (!over && filled) {
      filled = false;
      section.classList.remove('lsr__section--filled');
    }
  }

09. 유휴 시간 값이 지연 시간을 계산하는 위젯

유휴 시간 값이 지연 시간을 계산하는 위젯 지연 로딩
새 창에서 열기

09 · 유휴 시간 값이 지연 시간을 계산하는 위젯 지연 로딩 — 측정 누적면적 17.406% · 움직인 프레임 6/23GODRICH 원작 · zip 에 SCSS·React 포함 · 비밀번호 보기

IDLE_BUDGET_MSIDLE_TIMEOUT_MSIDLE_TICK_CAP_MS

requestIdleCallback 이 콜백마다 넘겨주는 유휴 남은시간 값을 누적해 목표 예산(IDLE_BUDGET_MS)에 닿는 순간에만 위젯 자리를 실제 내용으로 바꿉니다. 브라우저가 유휴 콜백을 지원하지 않을 때를 대비한 setTimeout 대체 경로도 함께 있습니다.

  // 계산 겹: requestIdleCallback 이 넘겨주는 유휴 남은시간(timeRemaining) 값을
  // 매 콜백마다 누적해, 누적값이 목표 예산(IDLE_BUDGET_MS)에 닿는 순간에만
  // 위젯 자리를 실제 내용으로 바꾼다.
  function tick(deadline) {
    if (!running) { return; }
    var got = deadline.didTimeout ? IDLE_TICK_CAP_MS : Math.min(deadline.timeRemaining(), IDLE_TICK_CAP_MS);
    accumulated = Math.min(IDLE_BUDGET_MS, accumulated + Math.max(got, 12));
    var pct = Math.round((accumulated / IDLE_BUDGET_MS) * 100);
    bar.style.width = pct + '%';
    sub.textContent = '유휴 시간 확보 중 ' + pct + '%';
    if (accumulated >= IDLE_BUDGET_MS) {
      widget.classList.add('liw__widget--loaded');
      running = false;
    } else {
      idleHandle = ric(tick, { timeout: IDLE_TIMEOUT_MS });
    }
  }

대시보드 사이드바·매장 소개 페이지 — 이 아홉 개를 실제로 붙이는 자리

사진이 스무 장 넘게 들어가는 상품 목록이나 취재 기사 페이지에는 03 의 교차 비율 계산이 그대로 맞습니다. 스크롤을 천천히 내리는 방문자에게는 이미지가 하나씩 자연스럽게 또렷해지고, 스크롤을 빠르게 내리는 방문자에게는 교차 비율이 한 번에 기준을 넘어가면서 페이드인이 짧게 끝나므로 어느 쪽이든 화면이 갑자기 비어 보이지 않습니다.

가게·행사 소개 페이지의 오시는 길 영역에는 07 의 클릭 로드 구조가 맞습니다. 지도 embed 는 그 자체로 무겁고, 페이지를 열자마자 자동으로 불러오면 정작 필요 없는 방문자에게도 그 무게를 떠넘기게 됩니다. 클릭한 자리에서 리플이 번져 지도로 바뀌는 구조는 「보고 싶을 때만 불러온다」는 신호를 화면에서 그대로 보여줍니다. 같은 embed 자리표시자 문제는 블로그 위젯 디자인 9종 — 공유·후기·지도 임베드 카드 편에서 카드 형태로 더 다룹니다.

콘텐츠가 긴 페이지의 사이드바 추천·광고 위젯에는 09 의 유휴 시간 계산이 맞습니다. 본문을 읽는 동안 브라우저가 한가한 틈을 골라 무거운 위젯을 뒤늦게 불러오므로, 스크롤이나 클릭 같은 실제 조작을 방해하지 않습니다. 본문이 다 그려지기 전에 무거운 것부터 그리는 순서를 뒤집는다는 점에서는 무한 스크롤 로딩 UI 패턴 9종 — 스피너부터 가상 윈도우까지 편의 우선순위 계산과 방향이 같습니다.

23프레임 실측에서 05·09 항목이 유독 조용한 이유

항목 누적면적 강도 평균 움직인 프레임
01 헤드라인 폰트 전환 5.24% 55.1 10/23
02 배경 해상도 전환 38.08% 42.8 9/23
03 이미지 페이드인 36.04% 79.2 23/23
04 아바타 이니셜 1.73% 46.5 8/23
05 갤러리 스태거 20.57% 73.9 7/23
06 영상 포스터 전환 21.26% 58.4 15/23
07 지도 iframe 리플 21.26% 34.8 10/23
08 아래 섹션 채움 16.77% 66.1 12/23
09 위젯 유휴 로딩 17.41% 82.1 6/23

23프레임을 찍는 동안 05 는 7 프레임에서만, 09 는 6 프레임에서만 화면이 실제로 바뀌었습니다. 이 편의 아홉 개는 대부분 한 번 로드되면 다시 움직이지 않는 「일회성 전환」이라, 스태거가 끝나거나 유휴 예산을 다 채우고 나면 나머지 프레임은 정지한 최종 상태를 그대로 유지합니다. 03 처럼 스크롤을 계속 움직여 교차 비율이 프레임마다 다시 계산되는 항목만 23 프레임 내내 값이 바뀌고, 나머지는 전환이 짧게 끝난 뒤 조용해지는 쪽이 정상입니다.

언마운트해도 타이머는 계속 돕니다 — 실제로 깨지는 지점

아홉 개 전부 화면 요소를 걷어낼 때 타이머·유휴 콜백을 정리하는 코드가 없습니다. is-demo 클래스가 남아 있는 동안만 재생 루프가 자기 자신을 다시 예약하는 식으로 멈추게 짜여 있는데, 싱글 페이지 앱에서 라우트를 바꿔 이 컴포넌트를 DOM 에서 통째로 떼어내는 경우에는 그 클래스 검사를 할 기회 자체가 사라집니다. 05 의 스태거 setTimeout 아홉 개, 09 의 requestIdleCallback 재예약처럼 다음 프레임을 스스로 다시 예약하는 코드일수록 컴포넌트를 실제 프로젝트에 옮길 때는 언마운트 시점에 타이머 id 를 모아 clearTimeout·cancelIdleCallback 으로 정리하는 코드를 한 줄 더 얹어야 안전합니다. 데모 화면에서는 탭을 오래 열어 둬도 is-demo 클래스가 그대로 남아 있어 이 문제가 드러나지 않으므로, 실제 프로젝트에 옮겨 붙이기 전까지는 눈에 띄지 않는 종류의 함정입니다.

또 하나는 실제 리소스로 바꿔 붙일 때 생깁니다. 이 아홉 개는 794sbpwn 압축을 풀어 실제 파일을 열어 봐도 이미지·영상·지도가 전부 시뮬레이션이라, <img loading="lazy"> 의 진짜 로드 이벤트나 iframe 의 진짜 load 이벤트로 바꿔 붙이는 순간 지금 값(경과 시간·스크롤 위치)만으로는 로드 완료 시점을 못 잡는 경우가 생깁니다. 03·08 처럼 관찰 값으로 판정하는 쪽은 그대로 옮겨도 되지만, 01·02·06 처럼 정해 둔 시간이 지나면 그대로 로드 완료로 넘기는 쪽은 실제 리소스의 load·loadeddata 이벤트를 값으로 바꿔 끼워야 합니다.

키보드·스크린리더에서 확인할 것

01·02·05·07 은 클릭뿐 아니라 Enter·Space 키로도 같은 계산을 실행하도록 키보드 리스너를 함께 듣습니다. 04·06·09 처럼 버튼 하나만 두는 항목은 그 버튼이 원래부터 키보드 포커스를 받는 요소인지부터 확인해야 합니다. 스크린리더 사용자에게는 자리표시자와 실제 콘텐츠가 바뀌는 순간을 aria-live 문구(배지 텍스트)로 전달하는 편이, 화면 전환만으로 알리는 것보다 안전합니다.

접근성을 지키면서 임계값·스태거 지연·유휴 예산만 바꾸면 체감이 달라집니다

항목 값 느낌
03 INTERSECTION_THRESHOLD 0.4 → 0.25 이미지가 뷰포트에 조금만 걸쳐도 더 일찍 페이드인을 시작합니다.
05 STAGGER_STEP_MS 90 → 160 그리드 뒤쪽 썸네일일수록 나타나기까지 걸리는 시간 차이가 커집니다.
09 IDLE_BUDGET_MS 260 → 420 무거운 위젯이 화면에 뒤늦게 나타나기까지 걸리는 시간이 길어집니다.

prefers-reduced-motion 이 참일 때 아홉 개 모두 transition·requestAnimationFrame 트윈을 건너뛰고 최종 상태로 곧바로 스냅합니다. 03 은 블러·스케일 보간 없이 바로 선명한 이미지로, 05 는 칸마다 시간차 없이 한 번에 실제 사진으로, 09 는 진행률 막대 애니메이션 없이 곧바로 로드 완료 상태로 바뀝니다. 자세한 계산 근거는 prefers-reduced-motion 문서에 정리돼 있습니다.

지연 로딩 이란 무엇이고, 자리표시자는 왜 필요할까요?

이 방식은 화면에 필요한 리소스를 처음부터 전부 내려받지 않고, 실제로 보일 시점이 가까워졌을 때 값 하나(스크롤 위치·교차 비율·유휴 시간·클릭 여부)를 계산해 그 시점에만 불러오는 것을 말합니다. 사진이 많은 페이지에서는 이미지 레이지 로딩이라는 표현으로도 흔히 불립니다. 자리표시자가 없으면 리소스를 기다리는 동안 화면이 빈 채로 있거나 레이아웃이 갑자기 밀리는데, 이 편의 아홉 개는 그 대기 시간에 색·이니셜·링 같은 임시 화면을 먼저 채워 레이아웃을 붙잡아 둡니다.

같은 「계산이 끝나기 전까지 임시 화면을 보여준다」는 접근은 레이지 로딩 말고도 널리 씁니다. 로딩 스켈레톤 ui 9종 — 차트가 계산으로 완성되는 순간까지 편은 값이 없는 상태(0)에서 시작해 데이터가 들어오는 대로 모양을 채워 가는 쪽이고, 이 편은 값이 이미 정해진 리소스를 언제 보여줄지 시점만 계산하는 쪽이라는 차이가 있습니다.

이 아홉 개를 실제 프로젝트에 붙이기 전에 자주 나오는 질문

이 아홉 개를 실제 프로젝트에 그대로 옮겨 붙여도 되나요?

계산 로직(값이 어디서 오고 무엇을 바꾸는지)은 그대로 옮겨도 됩니다. 다만 앞서 짚은 대로 setTimeout 으로 정해 둔 지연 시간을 실제 리소스의 load 이벤트 값으로 바꾸고, 컴포넌트를 걷어낼 때 타이머를 정리하는 코드를 더해야 안전합니다.

04 항목이 실제 사진 대신 이니셜을 먼저 보여주는 이유는 무엇인가요?

프로필 사진은 사용자가 올리기 전까지는 없을 수도 있고, 올렸더라도 네트워크가 느리면 늦게 도착합니다. 이름 값에서 계산한 이니셜과 색 배지를 먼저 보여주면, 사진이 없거나 늦게 오는 경우에도 화면이 완전히 비어 보이지 않습니다.

지연 로딩을 처음 켜면 자리표시자가 왜 먼저 보이나요?

값을 계산하는 데 걸리는 시간이 0 이 아니기 때문입니다. 스크롤 위치가 임계치를 넘는지, 유휴 시간이 예산에 닿았는지, 클릭이 있었는지를 판정하기 전까지는 아직 「로드 완료」로 볼 근거가 없으므로, 그 사이에는 자리표시자가 화면을 대신 채웁니다.

압축 비밀번호를 입력하세요

비밀번호는 이 글 본문 안에 있습니다. 읽다 보면 나옵니다.