Cumulative Layout Shift (CLS)

브라우저 지원

  • Chrome: 77
  • Edge: 79
  • Firefox: 지원되지 않음
  • Safari: 지원되지 않음

소스

예상치 못한 레이아웃 전환은 텍스트가 갑자기 이동하여 읽는 도중에 위치를 놓치는 것부터 잘못된 링크나 버튼을 클릭하게 하는 것까지 다양한 방식으로 사용자 경험을 방해할 수 있습니다. 경우에 따라 심각한 손상을 입을 수 있습니다.

레이아웃이 갑자기 바뀌면서 사용자가 취소하려는 대규모 주문을 확인하게 됩니다.

페이지 콘텐츠가 예기치 않게 이동하는 경우는 일반적으로 리소스가 비동기식으로 로드되거나 DOM 요소가 기존 콘텐츠보다 먼저 페이지에 동적으로 추가될 때 발생합니다. 레이아웃이 전환되는 이유는 크기를 알 수 없는 이미지나 동영상, 초기 대체 값보다 크거나 작은 글꼴, 크기를 동적으로 조절하는 서드 파티 광고 또는 위젯 때문일 수 있습니다.

개발 중인 사이트의 작동 방식과 사용자가 사이트를 경험하는 방식의 차이로 인해 이 문제가 더욱 악화됩니다. 예를 들면 다음과 같습니다.

  • 맞춤설정된 콘텐츠 또는 서드 파티 콘텐츠는 개발 환경과 프로덕션 환경에서 다르게 작동하는 경우가 많습니다.
  • 테스트 이미지는 개발자의 브라우저 캐시에 이미 있는 경우가 많지만 최종 사용자에게는 더 오래 로드됩니다.
  • 로컬에서 실행되는 API 호출은 너무 빠르기 때문에 개발 시에는 눈에 띄지 않는 지연이 프로덕션에서는 상당한 지연으로 이어질 수 있습니다.

누적 레이아웃 이동 (CLS) 측정항목을 사용하면 실제 사용자에게 이 문제가 발생하는 빈도를 측정하여 문제를 해결할 수 있습니다.

CLS란 무엇인가요?

CLS는 페이지의 전체 수명 주기 동안 발생하는 모든 예기치 않은 레이아웃 변경에 대한 레이아웃 변경 점수 중 가장 큰 버스트를 측정하는 측정항목입니다.

레이아웃 변경은 표시되는 요소가 렌더링된 프레임에서 다음 프레임으로 위치를 변경할 때마다 발생합니다. 개별 레이아웃 변경 점수가 계산되는 방식에 관한 자세한 내용은 이 가이드의 뒷부분에서 설명합니다.

세션 기간이라고 하는 레이아웃 전환 폭발은 하나 이상의 개별 레이아웃 전환이 각 전환 간에 1초 미만의 간격을 두고 빠르게 연속으로 발생하며 총 기간이 최대 5초인 경우를 말합니다.

가장 큰 버스트는 해당 기간 내 모든 레이아웃 전환의 누적 점수가 가장 큰 세션 기간입니다.

세션 창의 예시입니다. 파란색 막대는 각 개별 레이아웃 변경의 점수를 나타냅니다.

양호한 CLS 점수는 얼마인가요?

우수한 사용자 환경을 제공하기 위해 사이트의 CLS 점수가 0.1 미만이 되도록 노력해야 합니다. 대부분의 사용자가 이 목표를 달성하도록 하려면 모바일 및 데스크톱 기기별로 분류된 페이지 로드의 75번째 백분위수를 측정하는 것이 좋습니다.

양호한 CLS 값은 0.1 이하이고, 나쁜 값은 0.25를 초과하며, 그 사이의 값은 개선이 필요합니다.
CLS 값이 0.1 이하이면 좋습니다. 나쁜 값은 0.25보다 큽니다.

이 권장사항의 근거가 되는 연구 및 방법론에 대해 자세히 알아보려면 Core Web Vitals 측정항목 기준 정의를 참고하세요.

레이아웃 변경 세부정보

레이아웃 전환은 뷰포트 내에 표시되는 요소가 두 프레임 간에 시작 위치 (예: 기본 작성 모드의 상단 및 왼쪽 위치)를 변경할 때마다 layout-shift 항목을 보고하는 Layout Instability API에 의해 정의됩니다. 이러한 요소는 불안정한 요소로 간주됩니다.

레이아웃은 기존 요소의 시작 위치가 변경될 때만 이동합니다. 새 요소가 DOM에 추가되거나 기존 요소의 크기가 변경되는 경우, 변경사항으로 인해 다른 표시된 요소의 시작 위치가 변경되지 않는 한 레이아웃 전환으로 간주되지 않습니다.

레이아웃 변경 점수

레이아웃 변경 점수를 계산하기 위해 브라우저는 렌더링된 두 프레임 간의 표시 영역 크기와 표시 영역 내 불안정한 요소의 움직임을 확인합니다. 레이아웃 이동 점수는 움직임의 두 측정값인 영향도 분수거리 분수 (둘 다 아래에 정의됨)의 곱입니다.

layout shift score = impact fraction * distance fraction

영향 분수

영향 분수불안정한 요소가 두 프레임 사이의 표시 영역에 미치는 영향을 측정합니다.

특정 프레임의 영향 분율은 해당 프레임과 이전 프레임의 모든 불안정한 요소의 표시 영역을 뷰포트의 총 영역으로 나눈 값입니다.

불안정한 요소가 하나 있는 영향도 분율 예시
요소의 위치가 변경되면 이전 위치와 현재 위치가 모두 충격 분수에 기여합니다.

위 이미지에는 한 프레임에서 뷰포트의 절반을 차지하는 요소가 있습니다. 그런 다음 다음 프레임에서 요소가 뷰포트 높이의 25% 만큼 아래로 이동합니다. 빨간색 점선 직사각형은 두 프레임에서 요소의 표시 영역의 합집합을 나타냅니다. 이 경우 총 뷰포트의 75% 이므로 영향 분율0.75입니다.

거리 분수

레이아웃 변경 점수 방정식의 다른 부분은 불안정한 요소가 표시 영역을 기준으로 이동한 거리를 측정합니다. 거리 분수는 프레임에서 불안정한 요소가 이동한 최대 가로 또는 세로 거리를 표시 영역의 최대 크기 (너비 또는 높이 중 더 큰 값)로 나눈 값입니다.

불안정한 요소가 하나 있는 거리 분수 예
거리 분수는 요소가 표시 영역에서 얼마나 이동했는지를 측정합니다.

이전 예에서 가장 큰 뷰포트 크기는 높이이며 불안정한 요소가 뷰포트 높이의 25% 만큼 이동하여 거리 분수가 0.25가 됩니다.

따라서 이 예에서 영향도 분수0.75이고 거리 분수0.25이므로 레이아웃 이동 점수0.75 * 0.25 = 0.1875입니다.

다음 예는 기존 요소에 콘텐츠를 추가하면 레이아웃 전환 점수에 어떤 영향을 미치는지 보여줍니다.

안정적이고 _불안정한 요소_가 여러 개인 레이아웃 전환 예
회색 상자의 하단에 버튼을 추가하면 녹색 상자가 아래로 밀려 표시 영역에서 일부가 사라집니다.

이 예에서 회색 상자는 크기가 변경되지만 시작 위치는 변경되지 않으므로 불안정한 요소가 아닙니다.

'Click Me!' 버튼은 이전에 DOM에 없으므로 시작 위치도 변경되지 않습니다.

하지만 초록색 상자의 시작 위치는 변경되지만, 일부가 뷰포트 밖으로 이동되었으므로 영향도 분수를 계산할 때는 보이지 않는 영역이 고려되지 않습니다. 두 프레임에서 녹색 상자의 표시 영역의 합집합(빨간색 점선 직사각형으로 표시됨)은 첫 번째 프레임의 녹색 상자 영역(표시 영역의 50%)과 같습니다. 영향 분수0.5입니다.

거리 분수는 보라색 화살표로 표시됩니다. 녹색 상자가 뷰포트의 약 14% 아래로 이동했으므로 거리 분수0.14입니다.

레이아웃 변경 점수는 0.5 x 0.14 = 0.07입니다.

다음 예는 여러 불안정한 요소가 페이지의 레이아웃 변경 점수에 미치는 영향을 보여줍니다.

안정적인 요소와 _불안정한 요소_, 표시 영역 잘림이 있는 레이아웃 변경 예시
정렬된 목록에 더 많은 이름이 표시되면 기존 이름이 이동하여 알파벳 순서가 유지됩니다.

위 이미지의 첫 번째 프레임에는 동물에 관한 API 요청의 결과 4개가 알파벳순으로 정렬되어 있습니다. 두 번째 프레임에서는 정렬된 목록에 더 많은 결과가 추가됩니다.

목록의 첫 번째 항목인 '고양이'는 프레임 간에 시작 위치가 변경되지 않으므로 안정적입니다. 마찬가지로 목록에 추가된 새 항목은 이전에 DOM에 없으므로 시작 위치도 변경되지 않습니다. 하지만 '개', '말', '얼룩말'이라고 라벨이 지정된 항목은 모두 시작 위치가 이동하므로 불안정한 요소입니다.

다시 말해 빨간색 점선 직사각형은 이러한 세 개의 불안정한 요소의 전후 영역의 합집합을 나타내며, 이 경우 뷰포트 영역의 약 60%(0.60영향 분율)입니다.

화살표는 불안정한 요소가 시작 위치에서 이동한 거리를 나타냅니다. 파란색 화살표로 표시된 'Zebra' 요소가 가장 많이 이동하여 표시 영역 높이의 약 30% 만큼 이동했습니다. 따라서 이 예시에서 거리 분수0.3입니다.

레이아웃 변경 점수는 0.60 x 0.3 = 0.18입니다.

예상되는 레이아웃 변경과 예상치 못한 레이아웃 변경

모든 레이아웃 전환이 나쁜 것은 아닙니다. 실제로 많은 동적 웹 애플리케이션은 페이지의 요소 시작 위치를 자주 변경합니다. 레이아웃 전환은 사용자가 예상하지 못한 경우에만 나쁩니다.

사용자가 시작하는 레이아웃 변경

사용자 상호작용 (예: 링크 클릭 또는 탭, 버튼 누르기, 검색창에 입력)에 대한 응답으로 발생하는 레이아웃 전환은 일반적으로 괜찮습니다. 단, 전환이 상호작용과 충분히 근접하여 사용자에게 관계가 명확하게 인식되어야 합니다.

예를 들어 사용자 상호작용이 완료하는 데 시간이 걸릴 수 있는 네트워크 요청을 트리거하는 경우 요청이 완료될 때 불쾌감을 주는 레이아웃 전환을 방지하기 위해 즉시 여백을 만들고 로드 표시기를 표시하는 것이 가장 좋습니다. 사용자가 로드 중임을 인지하지 못하거나 리소스가 언제 준비될지 알지 못하는 경우, 기다리는 동안 다른 항목을 클릭하려고 할 수 있습니다.

사용자 입력 후 500밀리초 이내에 발생하는 레이아웃 이동에는 hadRecentInput 플래그가 설정되므로 계산에서 제외할 수 있습니다.

애니메이션 및 전환

애니메이션과 전환은 잘 사용하면 사용자에게 놀라움을 주지 않고 페이지의 콘텐츠를 업데이트하는 좋은 방법입니다. 페이지에서 갑작스럽고 예상치 못하게 이동하는 콘텐츠는 거의 항상 사용자 경험을 저하시킵니다. 하지만 한 위치에서 다음 위치로 점진적이고 자연스럽게 이동하는 콘텐츠는 사용자가 상황을 더 잘 이해하고 상태 변경 간에 안내를 받을 수 있도록 도와줍니다.

일부 사이트 방문자는 애니메이션으로 인해 부정적인 영향을 받거나 주의력이 산만해질 수 있으므로 prefers-reduced-motion 브라우저 설정을 준수해야 합니다.

CSS transform 속성을 사용하면 레이아웃 전환을 트리거하지 않고 요소에 애니메이션을 적용할 수 있습니다.

  • heightwidth 속성을 변경하는 대신 transform: scale()를 사용하세요.
  • 요소를 이동하려면 top, right, bottom 또는 left 속성을 변경하지 말고 대신 transform: translate()를 사용하세요.

CLS 측정 방법

CLS는 실험실 또는 현장에서 측정할 수 있으며 다음 도구에서 사용할 수 있습니다.

현장 도구

실험실 도구

JavaScript에서 레이아웃 이동 측정

JavaScript에서 레이아웃 변경을 측정하려면 Layout Instability API를 사용합니다.

다음 예에서는 layout-shift 항목을 콘솔에 로깅하는 PerformanceObserver를 만드는 방법을 보여줍니다.

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('Layout shift:', entry);
  }
}).observe({type: 'layout-shift', buffered: true});

JavaScript에서 CLS 측정

JavaScript에서 CLS를 측정하려면 이러한 예기치 않은 layout-shift 항목을 세션으로 그룹화하고 최대 세션 값을 계산해야 합니다. CLS가 계산되는 방식에 관한 참조 구현이 포함된 web vitals JavaScript 라이브러리 소스 코드를 참고할 수 있습니다.

대부분의 경우 페이지가 언로드될 때의 현재 CLS 값이 해당 페이지의 최종 CLS 값이지만 다음 섹션에 설명된 몇 가지 중요한 예외가 있습니다. web vitals JavaScript 라이브러리는 웹 API의 제한사항 내에서 최대한 이를 고려합니다.

측정항목과 API의 차이점

  • 페이지가 백그라운드에서 로드되거나 브라우저가 콘텐츠를 페인팅하기 전에 백그라운드로 전환된 경우 CLS 값을 보고해서는 안 됩니다.
  • 페이지가 뒤로/앞으로 캐시에서 복원되면 사용자에게 별도의 페이지 방문으로 인식되므로 CLS 값을 0으로 재설정해야 합니다.
  • API는 iframe 내에서 발생하는 시프트에 대한 layout-shift 항목을 보고하지 않지만 측정항목은 페이지의 사용자 환경의 일부이므로 이를 보고합니다. 이로 인해 CrUX와 RUM 간에 차이가 발생할 수 있습니다. CLS를 올바르게 측정하려면 이를 고려해야 합니다. 하위 프레임은 API를 사용하여 집계를 위해 layout-shift 항목을 상위 프레임에 보고할 수 있습니다.

이러한 예외에 더해 CLS는 페이지의 전체 수명을 측정하기 때문에 약간의 복잡성이 추가됩니다.

  • 사용자는 탭을 매우 오랫동안(일, 주, 월) 열어 둘 수 있습니다. 실제로 사용자는 탭을 닫지 않을 수도 있습니다.
  • 모바일 운영체제에서는 브라우저가 일반적으로 백그라운드 탭의 페이지 언로드 콜백을 실행하지 않으므로 '최종' 값을 보고하기가 어렵습니다.

이러한 경우를 처리하려면 페이지가 언로드될 때뿐만 아니라 백그라운드에 있을 때도 CLS를 보고해야 합니다 (visibilitychange 이벤트가 이러한 두 시나리오를 모두 다룹니다). 그러면 이 데이터를 수신하는 분석 시스템에서 백엔드에서 최종 CLS 값을 계산해야 합니다.

개발자는 이러한 모든 사례를 직접 기억하고 처리하는 대신 web-vitals JavaScript 라이브러리를 사용하여 CLS를 측정할 수 있습니다. 이 라이브러리는 iframe 사례를 제외하고 위에 언급된 모든 사례를 고려합니다.

import {onCLS} from 'web-vitals';

// Measure and log CLS in all situations
// where it needs to be reported.
onCLS(console.log);

CLS 개선 방법

현장에서 레이아웃 이동을 식별하고 실험실 데이터를 사용하여 이를 최적화하는 방법에 관한 자세한 내용은 CLS 최적화 가이드를 참고하세요.

추가 리소스

변경 로그

측정항목을 측정하는 데 사용되는 API에서 버그가 발견되는 경우가 있으며, 측정항목 자체의 정의에서 버그가 발견되는 경우도 있습니다. 따라서 변경이 필요할 때가 있으며 이러한 변경사항은 내부 보고서 및 대시보드에 개선 또는 회귀로 표시될 수 있습니다.

이를 관리하는 데 도움이 되도록 이러한 측정항목의 구현 또는 정의에 대한 모든 변경사항은 이 변경 로그에 표시됩니다.

이러한 측정항목에 관한 의견이 있으면 web-vitals-feedback Google 그룹에 제출해 주세요.