본문 바로가기
프론트엔드

레이아웃 시프트(Layout Shift)

by 느바 2025. 5. 5.
반응형

레이아웃 시프트(Layout Shift)

레이아웃 시프트(Layout Shift)는 웹 페이지가 로드되는 도중 요소들이 예기치 않게 움직이는 현상을 말합니다.

사용자가 페이지를 읽거나 상호작용하려는 순간에 콘텐츠가 갑자기 이동하면 불쾌한 사용자 경험을 유발할 수 있습니다. 이 현상은 주로 이미지, 광고, 폰트, iframe 등의 크기가 미리 지정되지 않았을 때 발생합니다.

Layout Shift의 원인

  1. 크기가 지정되지 않은 이미지
  2. 동적으로 로드되는 광고나 iframe
  3. 웹 폰트 로딩 지연 (FOIT/FOUT)
  4. 비동기적으로 삽입되는 DOM 요소

해결 방법

1. 이미지에 고정 크기 또는 aspect-ratio 지정

<!-- 예시: width와 height 명시 -->
<img src="image.jpg" width="600" height="400" alt="예시 이미지" />

<!-- CSS aspect-ratio를 사용하는 방법 -->
<style>
  .image-container {
    aspect-ratio: 3 / 2;
    width: 100%;
  }
  .image-container img {
    width: 100%;
    height: auto;
  }
</style>
<div class="image-container">
  <img src="image.jpg" alt="예시 이미지">
</div>

2. CSS로 미리 공간 확보

레이아웃에서 이미지나 광고가 들어올 공간을 미리 확보해두면 시프트를 줄일 수 있습니다.

3. 웹 폰트 지연 문제 해결

font-display: swap; 사용 → 시스템 폰트로 먼저 렌더링 후 웹폰트 로드

@font-face {
  font-family: 'MyFont';
  src: url('myfont.woff2') format('woff2');
  font-display: swap;
}

4. CLS (Cumulative Layout Shift) 측정 및 개선

Core Web Vitals의 하나인 CLS 지표를 사용하여 shift 현상을 수치화할 수 있습니다. Chrome DevTools, Lighthouse, Web Vitals API 등을 활용하세요.

5. Skeleton UI를 사용하는 해결 방법

Skeleton UI는 콘텐츠가 로드되기 전에 자리만 차지하고 있는 **회색 박스(또는 애니메이션된 로딩 상태)**를 보여주는 UI입니다. 이는 실제 콘텐츠가 로드될 때까지 레이아웃을 고정시켜주기 때문에 layout shift를 방지하는 데 효과적입니다.

Skeleton UI를 사용하는 해결 방법

예시: 이미지가 로딩될 때 Skeleton 적용

<style>
  .skeleton {
    background-color: #e0e0e0;
    width: 100%;
    padding-top: 66.66%; /* 3:2 비율 유지용 */
    position: relative;
    overflow: hidden;
  }

  .skeleton img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0;
    transition: opacity 0.3s ease-in-out;
  }

  .skeleton.loaded img {
    opacity: 1;
  }
</style>

<div class="skeleton" id="image-container">
  <img src="image.jpg" alt="예시 이미지" onload="this.parentNode.classList.add('loaded')" />
</div>

위 예제에서는 이미지가 로드되기 전까지 padding-top으로 비율을 확보하고, 이미지가 로드되면 자연스럽게 교체됩니다.

Skeleton UI의 장점

  • 예상 레이아웃 유지 → CLS 방지
  • 사용자에게 진행 중인 로딩 신호 제공
  • 자연스러운 전환으로 부드러운 UX

Skeleton UI 라이브러리 예시

import Skeleton from 'react-loading-skeleton';

function ImageLoader({ isLoading }) {
  return (
    <div style={{ width: 300, height: 200 }}>
      {isLoading ? (
        <Skeleton height={200} width={300} />
      ) : (
        <img src="image.jpg" width={300} height={200} alt="Loaded" />
      )}
    </div>
  );
}

 

 

Skeleton UI  React (with useState) 예시

import React, { useState } from 'react';

function ImageWithSkeleton() {
  const [loaded, setLoaded] = useState(false);

  return (
    <div
      style={{
        width: 300,
        height: 200,
        backgroundColor: loaded ? 'transparent' : '#e0e0e0',
        position: 'relative',
        overflow: 'hidden',
      }}
    >
      <img
        src="https://via.placeholder.com/300x200"
        alt="Sample"
        onLoad={() => setLoaded(true)}
        style={{
          width: '100%',
          height: '100%',
          objectFit: 'cover',
          opacity: loaded ? 1 : 0,
          transition: 'opacity 0.3s ease-in-out',
          position: 'absolute',
          top: 0,
          left: 0,
        }}
      />
    </div>
  );
}

export default ImageWithSkeleton;

Skeleton UI  Vue (Composition API)  예시

<template>
  <div :class="['skeleton', { loaded }]">
    <img
      src="https://via.placeholder.com/300x200"
      alt="Sample"
      @load="loaded = true"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue';

const loaded = ref(false);
</script>

<style scoped>
.skeleton {
  width: 300px;
  height: 200px;
  background-color: #e0e0e0;
  position: relative;
  overflow: hidden;
}
.skeleton img {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.3s ease-in-out;
}
.skeleton.loaded img {
  opacity: 1;
}
</style>

정리 

원인 해결 방법
이미지 크기 미지정 width/height 또는 aspect-ratio 지정
광고/iframe 고정된 크기 지정 또는 미리 자리 확보
웹폰트 지연 font-display: swap 사용
비동기 DOM 요소 삽입 공간 미리 확보 또는 애니메이션 사용
콘텐츠 로딩 지연 Skeleton UI로 자리 고정 후 콘텐츠 로딩 처리

'프론트엔드' 카테고리의 다른 글

CSS sahpe()  (0) 2025.05.10
AVIF, WebP  (0) 2025.05.05
SEO  (3) 2025.05.03
robots.txt 자동 생성  (0) 2025.05.03
xml-sitemaps.com  (1) 2025.05.03