누적 레이아웃 이동 최적화
갑작스러운 레이아웃 이동을 방지해 사용자 경험을 개선하는 방법을 알아봅니다.
"저걸 클릭하려고 했는데! 왜 움직인 거야? 😭"
레이아웃 변경은 사용자의 주의를 산만하게 할 수 있습니다. 글을 읽기 시작했는데, 갑자기 요소가 페이지 아래로 이동해서 읽었던 곳을 다시 찾아야 한다고 생각해보세요. 이런 상황은 뉴스를 읽거나 '검색' 또는 '장바구니에 추가' 버튼을 클릭하려고 할 때 등 웹에서 자주 경험할 수 있는 일입니다. 이러한 경험은 시각적으로 거슬리는 데다가 짜증스럽죠. 다른 요소가 갑자기 페이지에 추가되거나 크기 조정으로 인해 시각적 요소가 강제로 이동할 때 자주 발생하는 현상입니다.
Cumulative Layout Shift(누적 레이아웃 이동, CLS)는 CoreWeb Vitals 메트릭으로, 사용자 입력 500ms 이내에 발생하지 않는 레이아웃 이동에 대한 점수를 합산하여 콘텐츠의 불안정성을 측정합니다. 뷰포트에서 가시적인 콘텐츠가 얼마나 이동했는지와 영향을 받은 요소가 이동한 거리를 확인합니다.
이 가이드에서는 레이아웃 이동의 일반적인 원인을 최적화하는 방법을 다룹니다.
열악한 CLS 값이 발생하는 일반적인 이유는 다음과 같습니다.
- 크기가 정해지지 않은 이미지
- 크기가 정해지지 않은 광고, 임베드 및 iframe
- 동적으로 주입된 콘텐츠
- FOIT/FOUT을 유발하는 웹 글꼴
- DOM을 업데이트하기 전에 네트워크 응답을 대기하는 작업
크기가 정해지지 않은 이미지 🌆 #
요약: 이미지 및 비디오 요소에 항상 width
및 height
크기 속성을 포함하세요. 또는 CSS 종횡비 상자를 사용하여 필요한 공간을 확보해 두는 방법도 좋습니다. 이러한 접근 방식을 통해 이미지가 로드되는 동안 브라우저가 문서에 올바른 양의 공간을 할당할 수 있습니다.
역사 #
웹 초기에 개발자는 브라우저가 이미지 가져오기를 시작하기 전에 페이지에 충분한 공간이 할당되었는지 확인하기 위해 <img>
태그에 width
및 height
속성을 추가하곤 했습니다. 이렇게 하면 재배치나 레이아웃 재구성을 최소화할 수 있기 때문이었죠.
<img src="puppy.jpg" width="640" height="360" alt="강아지와 풍선" />
width
와 height
에는 단위가 포함되지 않는다는 것을 알 수 있습니다. 이러한 "픽셀" 크기는 640x360의 공간이 확보되도록 합니다. 실제 크기가 일치하는지와는 관계없이 이미지는 이 공간에 맞게 조정됩니다.
그러다가 반응형 웹 디자인이 도입되며 width
와 height
를 생략하고, 대신 CSS를 사용하여 이미지 크기를 조정하기 시작했습니다.
img {
width: 100%; /* or max-width: 100%; */
height: auto;
}
이 접근 방식의 단점은 이미지가 다운로드되기 시작하고 브라우저가 크기를 결정할 수 있을 때만 이미지에 대한 공간을 할당할 수 있다는 것입니다. 이미지가 로드되면 각 이미지가 화면에 나타날 때 페이지가 재배치되면서 텍스트가 갑자기 화면 아래로 사라져 버리는 것이 일반적이었습니다. 전혀 원활한 사용자 경험을 제공하지 못했죠.
여기서 종횡비가 등장하게 됩니다. 이미지의 종횡비란 너비와 높이의 비율로, 콜론으로 구분된 두 개의 숫자로 표현하는 것이 일반적입니다(예: 16:9 또는 4:3). x:y 종횡비의 경우 이미지의 너비는 x 단위이고 높이는 y 단위입니다.
즉, 크기 중 하나를 알고 있으면 다른 크기를 결정할 수 있다는 의미입니다. 16:9 종횡비의 경우 다음과 같이 계산됩니다.
- puppy.jpg의 높이가 360px인 경우 너비는 360 x (16 / 9) = 640px입니다.
- puppy.jpg의 너비가 640픽셀인 경우 높이는 640 x (9 / 16) = 360픽셀입니다.
종횡비를 알면 브라우저에서 높이 및 관련 영역에 대한 충분한 공간을 계산하고 할당할 수 있습니다.
최신 모범 사례 #
최신 브라우저는 이제 이미지의 너비 및 높이 속성을 기반으로 이미지의 기본 종횡비를 설정하므로 레이아웃 이동을 방지하도록 설정하는 것이 중요합니다. CSS Working Group 덕분에 개발자는 width
와 height
를 일반적으로 설정하기만 하면 됩니다.
<!-- set a 640:360 i.e a 16:9 - aspect ratio -->
<img src="puppy.jpg" width="640" height="360" alt="강아지와 풍선" />
… 그리고 모든 브라우저의 UA 스타일시트 는 요소의 기존 width
및 height
속성을 기반으로 기본 종횡비를 추가합니다.
img {
aspect-ratio: attr(width) / attr(height);
}
이것은 이미지가 로드되기 전에 width
및 height
속성을 기반으로 종횡비를 계산하고 레이아웃 계산이 시작되는 그 시점에 이러한 정보를 제공합니다. 이미지에 특정 너비(예: width: 100%
)가 지정되면 종횡비를 사용하여 높이를 계산합니다.
팁: 종횡비를 파악하기가 어렵다면 편리한 계산기를 사용할 수 있습니다.
위 이미지 종횡비 변화는 Firefox. Chromium에 제공되었으며 WebKit(Safari)에 적용됩니다.
반응형 이미지와 종횡비에 대해 더욱 심층적으로 알아보려면 미디어 종횡비를 사용해 원활하게 페이지 로드하기를 참조하세요
이미지가 컨테이너 안에 있다면 CSS를 사용하여 이 컨테이너의 너비에 맞게 이미지 크기를 조정할 수 있습니다. 여기에서는 이미지 높이의 값이 고정되지 않도록(예: 360px
) height: auto;
를 설정합니다.
img {
height: auto;
width: 100%;
}
그렇다면 반응형 이미지는요?
반응형 이미지 작업 시에는 srcset
을 통해 브라우저에서 선택하고 각 이미지의 크기를 결정하도록 허용할 이미지를 정의합니다. <img>
너비 및 높이 속성을 설정할 수 있도록 하려면 각 이미지가 동일한 종횡비를 사용해야 합니다.
<img
width="1000"
height="1000"
src="puppy-1000.jpg"
srcset="puppy-1000.jpg 1000w, puppy-2000.jpg 2000w, puppy-3000.jpg 3000w"
alt="강아지와 풍선"
/>
아트 디렉션은요?
페이지는 데스크톱에 표시되는 전체 이미지와 함께 좁은 뷰포트에 이미지의 잘린 부분을 포함하려고 할 수 있습니다.
<picture>
<source media="(max-width: 799px)" srcset="puppy-480w-cropped.jpg" />
<source media="(min-width: 800px)" srcset="puppy-800w.jpg" />
<img src="puppy-800w.jpg" alt="강아지와 풍선" />
</picture>
이러한 이미지는 다른 종횡비를 가질 수 있으며 브라우저는 여전히 모든 소스에서 크기를 지정하는 방법을 비롯해 가장 효율적인 솔루션이 무엇인지 평가하고 있습니다. 솔루션이 결정될 때까지는 여전히 레이아웃 재구성이 발생할 가능성이 있습니다.
크기가 정해지지 않은 광고, 임베드 및 iframe 📢😱 #
광고 #
광고는 웹에서 레이아웃 이동에 가장 크게 기여하는 요소 중 하나입니다. 광고 네트워크 및 게시자는 동적 광고 크기를 지원하는 경우가 많습니다. 광고의 크기는 경매에서 더 높은 클릭률과 더 많은 광고로 이어져 성능/수익을 높이는 요소이기 때문이죠. 안타깝게도 광고는 사용자가 보는 콘텐츠를 페이지 아래로 밀어내기 때문에 최적의 사용자 경험과는 멀어지게 됩니다.
광고 수명 주기 동안 여러 지점에서 레이아웃 이동이 발생할 수 있습니다.
- 사이트가 DOM에 광고 컨테이너를 삽입하는 경우
- 사이트에서 자사 코드로 광고 컨테이너의 크기를 조정하는 경우
- 광고 태그 라이브러리가 로드되고 광고 컨테이너의 크기가 조정되는 경우
- 광고가 컨테이너를 채우며 최종 광고의 크기가 달라 크기를 조정하는 경우
다행히도 사이트에서는 광고 이동을 줄이기 위한 모범 사례를 따를 수 있습니다. 사이트는 다음을 통해 이러한 레이아웃 이동을 경감시킬 수 있습니다.
- 광고 슬롯을 위한 고정 공간을 확보합니다.
- 즉, 광고 태그 라이브러리가 로드되기 전에 요소의 스타일을 지정합니다.
- 콘텐츠 흐름에 따라 광고를 배치하는 경우 슬롯 크기를 미리 확보하여 이동이 없도록 합니다. 이러한 광고는 화면 밖에서 로드되는 경우 레이아웃 이동을 일으키지 않아야 합니다.
- 뷰포트 상단 근처에 비고정 광고를 배치하는 경우 주의를 기울여야 합니다.
- 아래 예에서는 광고를 "world vision" 로고 아래로 이동하고 해당 슬롯에 대한 충분한 공간을 확보하는 것이 좋습니다.
- 자리 표시자를 표시하여 광고 슬롯이 표시될 때 반환된 광고가 없는 경우 할당된 공간이 축소되지 않도록 합니다.
- 광고 슬롯에 대해 가능한 가장 큰 크기를 할당해 이동이 없도록 합니다.
- 이것 또한 방법이긴 하지만, 더 작은 광고 크리에이티브가 해당 슬롯을 채우면 빈 공간이 생길 위험이 있습니다.
- 과거 데이터를 기반으로 광고 슬롯에 가장 적합한 크기를 선택합니다.
일부 사이트에서는 광고 슬롯이 채워지지 않을 경우 초기에 슬롯을 축소하면 레이아웃 이동을 줄일 수 있다는 사실을 발견할 수도 있습니다. 게시되는 광고를 직접 제어하지 않는 한 매번 정확한 크기를 쉽게 선택할 수 있는 방법은 없습니다.
광고 슬롯을 위한 고정 공간 확보 #
태그 라이브러리에 전달된 동일한 크기로 슬롯 DOM 요소에 고정 스타일을 지정합니다. 이렇게 하면 라이브러리가 로드될 때 레이아웃 이동이 발생하지 않도록 할 수 있습니다. 이렇게 하지 않는 경우 라이브러리가 페이지 레이아웃 후 슬롯 요소의 크기를 변경할 수 있습니다.
더 작은 광고가 게시되는 경우도 고려해야 합니다. 더 작은 광고가 게재되는 경우 게시자는 레이아웃 이동을 피하기 위해 더 큰 컨테이너의 스타일을 지정할 수 있습니다. 이 접근 방식의 단점은 빈 공간의 양이 증가한다는 것으로, 균형에 신경을 써야 합니다.
뷰포트 상단 근처에 광고 배치하지 않기 #
뷰포트 상단 근처에 있는 광고는 중간에 있는 광고보다 더 큰 레이아웃 이동을 일으킬 수 있습니다. 이는 일반적으로 상단에 있는 광고가 더 많은 콘텐츠를 아래로 밀어내기 때문입니다. 즉, 광고가 이동을 발생시키는 경우 더 많은 요소가 이동한다는 의미죠. 반대로 뷰포트 중앙 근처에 있는 광고는 위쪽에 있는 콘텐츠가 이동할 가능성이 적으므로 그리 많은 요소가 이동하지 않는 경우가 많습니다.
임베드 및 iframe #
임베딩 가능한 위젯을 사용하면 페이지에 이식 가능한 웹 콘텐츠(예: YouTube의 비디오, Google Maps의 지도, 소셜 미디어 게시물 등)를 포함할 수 있습니다. 이러한 임베드는 다음과 같은 다양한 형식을 취할 수 있습니다.
- 대체를 멋진 임베딩으로 변환해주는 HTML 대체 및 JavaScript 태그
- 인라인 HTML 코드 조각
- iframe 임베드
이러한 임베드는 그 크기를 미리 알지 못하는 경우가 많습니다. 예를 들어 소셜 미디어 게시물의 경우 임베딩되는 것이 이미지인지 동영상인지 여러 줄의 텍스트인지 알 수 없습니다. 그 결과 임베드를 제공하는 플랫폼에서 항상 임베드에 대한 충분한 공간을 확보할 수 있는 것은 아니며, 이로 인해 최종적으로 로드될 때 레이아웃 이동이 발생할 수 있습니다.
이 문제를 해결하려면 자리 표시자 또는 대체가 있는 포함을 위한 충분한 공간을 미리 계산하여 CLS를 최소화할 수 있습니다. 포함에 사용할 수 있는 하나의 워크플로:
- 브라우저 개발자 도구를 사용하여 최종 임베드 높이를 확인합니다.
- 임베드가 로드되면 포함된 iframe의 크기가 해당 콘텐츠에 맞게 조정됩니다.
임베드를 위한 자리 표시자의 크기와 스타일을 기록해두세요. 미디어 쿼리를 사용하여 다양한 폼 팩터 간에 광고/자리 표시자 크기의 미묘한 차이를 설명해야 하는 경우도 있습니다.
동적 콘텐츠 📐 #
요약: 사용자 상호 작용에 대한 응답을 제외하고는 기존 콘텐츠 위에 콘텐츠를 삽입하는 것을 피하는 것이 좋습니다. 이렇게 하면 레이아웃 이동이 발생하기 때문입니다.
사이트를 로드하려고 할 때 뷰포트의 상단 또는 하단에 팝업되는 UI 때문에 레이아웃이 이동하는 것을 경험하신 적이 있을 겁니다. 광고와 마찬가지로 이는 페이지의 나머지 콘텐츠를 이동시키는 배너 및 양식이 있을 때 발생하는 경우가 많습니다.
"뉴스레터에 가입하세요!" (천천히 해! 우린 방금 만났잖아!)
"관련 콘텐츠"
"[iOS/Android] 앱 설치"
"주문하세요"
"GDPR 공지"
공간이 확보된 동적 콘텐츠.
이러한 유형의 UI 어포던스를 표시해야 하는 경우 미리 뷰포트에 충분한 공간을 확보하여(예: 자리 표시자 또는 스켈레톤 UI 사용) 로드 시 페이지의 콘텐츠가 크게 이동하는 일이 없도록 합니다.
동적으로 콘텐츠를 추가하는 것이 사용자 경험에 중요할 때도 있습니다. 예를 들어 항목 목록에 더 많은 제품을 로드하거나 라이브 피드 콘텐츠를 업데이트할 때와 같은 경우죠. 이러한 경우 예기치 않은 레이아웃 이동을 방지하는 몇 가지 방법이 있습니다.
- 고정 크기 컨테이너에서 이전 콘텐츠를 새 콘텐츠로 교체하거나 전환이 이루어진 후 캐러셀을 사용해 이전 콘텐츠를 제거합니다. 새 콘텐츠를 가져오는 동안 우발적인 클릭이나 탭을 방지하기 위해 전환이 완료될 때까지는 모든 링크와 컨트롤을 비활성화해야 합니다.
- 사용자가 새 콘텐츠 로드를 시작하도록 하여 이동을 예측할 수 있도록 합니다(예: "더 로드" 또는 "새로고침" 버튼 사용). 콘텐츠가 즉시 표시되도록 사용자 상호 작용 전에 콘텐츠를 미리 가져오는 것이 좋습니다. 참고로 사용자 입력 후 500ms 이내에 발생하는 레이아웃 이동은 CLS 계산에 포함되지 않습니다.
- 콘텐츠를 화면 밖에서 원활하게 로드하고 사용자에게 사용 가능하다는 메시지를 오버레이합니다(예: "위로 스크롤" 버튼 사용).

FOUT/FOIT를 유발하는 웹 글꼴 📝 #
웹 글꼴을 다운로드하고 렌더링하면 두 가지 방식으로 레이아웃이 변경될 수 있습니다.
- 대체 글꼴이 새 글꼴로 바뀝니다(FOUT - 스타일이 지정되지 않은 텍스트 플래시).
- "보이지 않는" 텍스트는 새 글꼴이 렌더링될 때까지 표시됩니다(FOIT - 보이지 않는 텍스트 플래시).
다음 도구를 사용하면 이를 최소화할 수 있습니다.
font-display
를 사용하면auto
,swap
,block
,fallback
및optional
과 같은 값으로 커스텀 글꼴의 렌더링 동작을 수정할 수 있습니다. 하지만 optional을 제외한 이 모든 값이 위와 같은 방식 중 하나로 레이아웃 재구성을 발생시킬 수 있습니다.- Font Loading API를 사용하면 필요한 글꼴을 가져오는 데 걸리는 시간을 줄일 수 있습니다.
Chrome 83부터 다음과 같은 방식을 추천합니다.
- 핵심 웹 글꼴에
<link rel=preload>
사용: 미리 로드된 글꼴은 최초 페인트를 충족할 가능성이 더 높으며 이 경우 레이아웃 이동이 발생하지 않습니다. <link rel=preload>
및font-display: optional
조합
자세한 내용은 선택적 글꼴 사전 로드로 레이아웃 이동 및 보이지 않는 텍스트의 깜박임(FOIT) 방지를 참조하세요.
애니메이션 🏃♀️ #
요약: 레이아웃 이동을 유발하는 속성의 애니메이션보다 transform
애니메이션을 권장합니다.
CSS 속성 값을 변경하면 브라우저가 이러한 변경 사항에 반응해야 할 수 있습니다. box-shadow
및 box-sizing
과 같은 많은 값이 레이아웃 재구성, 페인트 및 합성을 트리거합니다. 보다 비용이 적게 드는 방식으로 다수의 CSS 속성을 변경할 수 있습니다.
어떤 CSS 속성이 레이아웃을 트리거하는지 자세히 알아보려면 CSS 트리거 및 고성능 애니메이션을 참조하세요.
개발자 도구 🔧 #
다행히도 누적 레이아웃 이동(CLS)을 측정하고 디버그하는 데 사용할 수 있는 다양한 도구가 있습니다.
Lighthouse 6.0 이상에서는 실험실 환경에서 CLS 측정할 수 있도록 지원합니다. 이 버전에서는 가장 많은 레이아웃 이동을 일으키는 노드도 강조 표시해줍니다.

DevTools의 Performance 패널은 Chrome 84부터 Experience 섹션에 레이아웃 이동을 강조 표시해줍니다. Layout Shift
기록의 Summary 보기에는 영향을 받는 부분을 보여주는 직사각형 오버레이와 함께 누적 레이아웃 이동 점수를 표시합니다.Layout Shift
기록이 붉은색 막대와 함께 표시됩니다. 해당 기록을 클릭하면 영향을 받는 요소(예: Moved from/to 항목 참조)가 드릴다운됩니다.
Chrome User Experience Report를 사용해 원본 수준에서 집계된 실제 CLS를 측정할 수도 있습니다. CrUX CLS 데이터는 BigQuery를 통해 제공되며 CLS 성능을 확인하기 위한 샘플 쿼리도 사용할 수 있습니다.
이 가이드는 여기까지입니다. 페이지 이동을 줄이는 데 도움이 되기를 바랍니다. :)
검토해주신 Philip Walton, Kenji Baheux, Warren Maresca, Annie Sullivan, Steve Kobes 및 Gilberto Cocchi에게 감사드립니다.