레이아웃 변경 디버그

레이아웃 변경을 식별하고 수정하는 방법을 알아보세요.

케이티 헴페니우스
Katie Hempenius

이 문서의 첫 번째 부분에서는 레이아웃 변경 디버깅 도구에 관해 설명하고, 두 번째 부분에서는 레이아웃 변경 원인을 파악할 때 사용할 사고 과정을 설명합니다.

도구

Layout Instability API

Layout Instability API는 레이아웃 변경을 측정하고 보고하는 브라우저 메커니즘입니다. DevTools를 포함하여 레이아웃 변경을 디버깅하는 모든 도구는 궁극적으로 Layout Instability API를 기반으로 빌드됩니다. 그러나 Layout Instability API를 직접 사용하는 것은 유연성 때문에 강력한 디버깅 도구입니다.

사용

레이아웃 변경 횟수 (CLS)를 측정하는 동일한 코드 스니펫도 레이아웃 변경을 디버그하는 데 사용할 수 있습니다. 아래 스니펫은 콘솔로 레이아웃 이동에 관한 정보를 기록합니다. 이 로그를 검사하면 레이아웃 변경이 발생한 시점, 위치, 방식에 대한 정보를 확인할 수 있습니다.

let cls = 0;
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

이 스크립트를 실행할 때는 다음 사항에 유의하세요.

  • buffered: true 옵션은 PerformanceObserver가 브라우저의 성능 항목 버퍼에서 관찰자의 초기화 전에 생성된 성능 항목을 확인해야 함을 나타냅니다. 따라서 PerformanceObserver는 초기화되기 전과 후에 발생한 레이아웃 이동을 보고합니다. 콘솔 로그를 검사할 때 이 점에 유의하세요. 레이아웃 변경의 초기 과다로 인해 수많은 레이아웃 변경이 갑작스럽게 발생하는 것이 아니라 보고 백로그가 반영될 수 있습니다.
  • 성능에 영향을 미치지 않도록 PerformanceObserver는 기본 스레드가 유휴 상태가 될 때까지 기다렸다가 레이아웃 변경을 보고합니다. 따라서 기본 스레드의 바쁨 정도에 따라 레이아웃 변경이 발생하는 시점과 콘솔에 로깅되는 시점 사이에 약간의 지연이 발생할 수 있습니다.
  • 이 스크립트는 사용자 입력 500ms 이내에 발생한 레이아웃 변경을 무시하므로 CLS에 포함되지 않습니다.

레이아웃 변경 정보는 두 API, 즉 LayoutShiftLayoutShiftAttribution 인터페이스의 조합을 사용하여 보고됩니다. 이러한 각 인터페이스는 다음 섹션에서 더 자세히 설명합니다.

LayoutShift

각 레이아웃 변경은 LayoutShift 인터페이스를 사용하여 보고됩니다. 항목의 내용은 다음과 같습니다.

duration: 0
entryType: "layout-shift"
hadRecentInput: false
lastInputTime: 0
name: ""
sources: (3) [LayoutShiftAttribution, LayoutShiftAttribution, LayoutShiftAttribution]
startTime: 11317.934999999125
value: 0.17508567530168798

위 항목은 3개의 DOM 요소가 위치를 변경하는 동안 레이아웃 변경을 나타냅니다. 이 특정 레이아웃 변경의 레이아웃 변경 점수는 0.175입니다.

다음은 레이아웃 변경 디버깅과 가장 관련성이 높은 LayoutShift 인스턴스의 속성입니다.

속성 설명
sources sources 속성에는 레이아웃 변경 중에 이동한 DOM 요소가 나열됩니다. 이 배열에는 최대 5개의 소스를 포함할 수 있습니다. 레이아웃 변경의 영향을 받는 요소가 5개 이상인 경우 레이아웃 변경의 가장 큰 다섯 가지 원인 (레이아웃 안정성에 미치는 영향)이 보고됩니다. 이 정보는 LayoutShiftAttribution 인터페이스를 사용하여 보고됩니다 (아래에 더 자세히 설명되어 있음).
value value 속성은 특정 레이아웃 변경의 레이아웃 변경 점수를 보고합니다.
hadRecentInput hadRecentInput 속성은 사용자 입력 후 500밀리초 이내에 레이아웃 변경이 발생했는지를 나타냅니다.
startTime startTime 속성은 레이아웃 변경이 발생한 시점을 나타냅니다. startTime는 밀리초 단위로 표시되며 페이지 로드가 시작된 시간을 기준으로 측정됩니다.
duration duration 속성은 항상 0로 설정됩니다. 이 속성은 PerformanceEntry 인터페이스에서 상속됩니다 (LayoutShift 인터페이스는 PerformanceEntry 인터페이스를 확장함). 그러나 레이아웃 변경 이벤트에 지속 시간 개념이 적용되지 않으므로 0로 설정됩니다. PerformanceEntry 인터페이스에 관한 자세한 내용은 spec을 참고하세요.

LayoutShiftAttribution

LayoutShiftAttribution 인터페이스는 단일 DOM 요소의 단일 이동을 설명합니다. 레이아웃 변경 중에 여러 요소가 이동하면 sources 속성에 여러 항목이 포함됩니다.

예를 들어 아래 JSON은 소스가 하나인 레이아웃 변경(<div id='banner'> DOM 요소를 y: 76에서 y:246로 하향 이동)에 해당합니다.

// ...
  "sources": [
    {
      "node": "div#banner",
      "previousRect": {
        "x": 311,
        "y": 76,
        "width": 4,
        "height": 18,
        "top": 76,
        "right": 315,
        "bottom": 94,
        "left": 311
      },
      "currentRect": {
        "x": 311,
        "y": 246,
        "width": 4,
        "height": 18,
        "top": 246,
        "right": 315,
        "bottom": 264,
        "left": 311
      }
    }
  ]

node 속성은 이동한 HTML 요소를 식별합니다. DevTools에서 이 속성에 마우스를 가져가면 해당 페이지 요소가 강조표시됩니다.

previousRectcurrentRect 속성은 노드의 크기와 위치를 보고합니다.

  • xy 좌표는 요소의 왼쪽 상단 모서리에 해당하는 x 좌표와 y 좌표를 각각 보고합니다.
  • widthheight 속성은 각각 요소의 너비와 높이를 보고합니다.
  • top, right, bottom, left 속성은 요소의 지정된 가장자리에 해당하는 x 또는 y 좌표 값을 보고합니다. 즉, top 값은 y와 같고 bottom 값은 y+height와 같습니다.

previousRect의 모든 속성이 0으로 설정되면 요소가 뷰로 이동했다는 의미입니다. currentRect의 모든 속성이 0으로 설정되면 요소가 시야 밖으로 이동했다는 의미입니다.

이러한 출력을 해석할 때 이해해야 할 가장 중요한 사항 중 하나는 소스로 나열된 요소가 레이아웃 변경 중에 이동한 요소라는 점입니다. 그러나 이러한 요소는 레이아웃 불안정의 '근본 원인'과 간접적으로만 관련이 있을 수 있습니다. 예를 들면 다음과 같습니다.

예 1

이러한 레이아웃 변경은 단일 소스, 즉 요소 B로 보고됩니다. 그러나 이러한 레이아웃 변경의 근본 원인은 요소 A의 크기 변경입니다.

요소 크기 변경으로 인한 레이아웃 변경의 예

예 2

이 예에서 레이아웃 변경은 요소 A와 요소 B라는 두 가지 소스로 보고됩니다. 이러한 레이아웃 변경의 근본 원인은 요소 A의 위치 변경입니다.

요소 위치 변경으로 인한 레이아웃 변경의 예

예 3

이 예에서 레이아웃 변경은 요소 B라는 하나의 소스로 보고됩니다. B 요소의 위치를 변경하면 레이아웃이 변경되었습니다.

요소 위치 변경으로 인한 레이아웃 변경의 예

예 4

요소 B가 크기를 변경하지만 이 예에서는 레이아웃이 변경되지 않습니다.

크기가 변경되지만 레이아웃 변경을 일으키지 않는 요소의 예

Layout Instability API에서 DOM 변경사항을 보고하는 방식에 관한 데모를 확인하세요.

DevTools

성능 패널

DevTools Performance 패널의 Experience 창에는 특정 성능 트레이스 중에 발생하는 모든 레이아웃 변경이 표시됩니다. 이는 사용자 상호작용 후 500ms 이내에 발생하므로 CLS에 포함되지 않습니다. 환경 패널에서 특정 레이아웃 변경 위로 마우스를 가져가면 영향을 받는 DOM 요소가 강조표시됩니다.

DevTools Network 패널에 표시된 레이아웃 변경 스크린샷

레이아웃 변경에 관한 자세한 내용을 보려면 레이아웃 변경을 클릭한 다음 Summary 창을 엽니다. 요소 크기의 변경사항은 [width, height] 형식을 사용하여 나열됩니다. 요소 위치의 변경사항은 [x,y] 형식을 사용하여 나열됩니다. 최근 입력 있음 속성은 사용자 상호작용 후 500ms 이내에 레이아웃 변경이 발생했는지를 나타냅니다.

레이아웃 변경에 대한 DevTools &#39;Summary&#39; 탭의 스크린샷

레이아웃 변경 기간에 대한 자세한 내용은 이벤트 로그 탭을 여세요. Experience 창에서 빨간색 레이아웃 변경 직사각형의 길이를 확인하여 레이아웃 변경 시간을 근사화할 수도 있습니다.

레이아웃 변경을 위한 DevTools &#39;Event Log&#39; 탭의 스크린샷

Performance 패널 사용에 관한 자세한 내용은 성능 분석 참조를 참고하세요.

레이아웃 변경 영역 강조표시

레이아웃 변경 영역을 강조표시하면 페이지에서 발생하는 레이아웃 변경의 위치와 타이밍을 빠르게 한눈에 파악할 수 있습니다.

DevTools에서 Layout Shift Region을 사용 설정하려면 Settings > More Tools > Rendering > Layout Shift regions로 이동한 다음 디버그하려는 페이지를 새로고침하세요. 레이아웃 변경 영역은 잠시 보라색으로 강조표시됩니다.

레이아웃 변경의 원인을 식별하기 위한 고려 프로세스

아래 단계를 사용하여 레이아웃 변경이 발생하는 시점이나 방법과 관계없이 레이아웃 변경의 원인을 파악할 수 있습니다. 이 단계는 Lighthouse 실행을 통해 보완할 수 있지만 Lighthouse는 초기 페이지 로드 중에 발생한 레이아웃 변경만 식별할 수 있습니다. 또한 Lighthouse는 레이아웃 변경의 몇 가지 원인(예: 명시적인 너비와 높이가 없는 이미지 요소)에 관한 제안만 제공할 수 있습니다.

레이아웃 변경의 원인 식별

레이아웃 변경은 다음과 같은 이벤트로 인해 발생할 수 있습니다.

  • DOM 요소의 위치 변경사항
  • DOM 요소 크기 변경
  • DOM 요소 삽입 또는 제거
  • 레이아웃을 트리거하는 애니메이션

특히 이동한 요소 바로 앞에 있는 DOM 요소는 레이아웃 변경을 '일으킬' 가능성이 가장 높은 요소입니다. 따라서 레이아웃 변경이 발생한 이유를 조사할 때는 다음을 고려하세요.

  • 이전 요소의 위치나 크기가 변경되었나요?
  • 이동한 요소보다 먼저 DOM 요소가 삽입되거나 삭제되었나요?
  • 이동한 요소의 위치가 명시적으로 변경되었나요?

이전 요소로 인해 레이아웃 변경이 발생하지 않은 경우 다른 이전 요소와 주변 요소를 고려하여 검색을 계속합니다.

또한 레이아웃 변경의 방향과 거리를 통해 근본 원인에 관한 힌트를 얻을 수 있습니다. 예를 들어 아래로 크게 이동하면 DOM 요소가 삽입되었음을 나타내는 반면, 1픽셀 또는 2픽셀 레이아웃 변경은 충돌하는 CSS 스타일이 적용되거나 웹 글꼴이 로드 및 적용되었음을 나타내는 경우가 많습니다.

글꼴 변경으로 인한 레이아웃 변경을 보여주는 다이어그램
이 예에서는 글꼴 바꾸기로 인해 페이지 요소가 5픽셀 위쪽으로 이동했습니다.

다음은 레이아웃 변경 이벤트를 가장 자주 일으키는 특정 동작입니다.

요소의 위치 변경 (다른 요소의 이동으로 인한 것이 아님)

이러한 유형의 변경은 주로 다음과 같은 원인으로 인해 발생합니다.

  • 늦게 로드되거나 이전에 선언된 스타일을 덮어쓰는 스타일시트
  • 애니메이션 및 전환 효과

요소의 크기 변경

이러한 유형의 변경은 주로 다음과 같은 원인으로 인해 발생합니다.

  • 늦게 로드되거나 이전에 선언된 스타일을 덮어쓰는 스타일시트
  • '슬롯'이 렌더링된 후 로드되는 widthheight 속성이 없는 이미지와 iframe
  • 텍스트가 렌더링된 후 글꼴을 바꾸는 width 또는 height 속성이 없는 텍스트 블록

DOM 요소 삽입 또는 제거

이 문제는 보통 다음과 같은 이유로 발생합니다.

  • 광고 및 기타 서드 파티 삽입 콘텐츠 삽입
  • 배너, 알림 및 모달 삽입
  • 무한 스크롤 및 기존 콘텐츠 위에 추가 콘텐츠를 로드하는 기타 UX 패턴

레이아웃을 트리거하는 애니메이션

일부 애니메이션 효과는 레이아웃을 트리거할 수 있습니다. 이에 대한 일반적인 예는 CSS의 transform 속성을 사용하는 대신 top 또는 left와 같은 속성을 증분하여 DOM 요소를 '애니메이션'하는 경우입니다. 자세한 내용은 고성능 CSS 애니메이션을 만드는 방법을 참고하세요.

레이아웃 변경 재현

재현할 수 없는 레이아웃 변경은 수정할 수 없습니다. 사이트의 레이아웃 안정성을 더 잘 파악할 수 있는 가장 간단하면서도 가장 효과적인 방법 중 하나는 레이아웃 변경을 트리거하는 것을 목표로 5~10분 동안 사이트와 상호작용하는 것입니다. 이 작업을 실행하는 동안 콘솔을 열어 두고 Layout Instability API를 사용하여 레이아웃 변경을 보고합니다.

찾기 어려운 레이아웃 변경의 경우 다른 기기와 연결 속도로 이 연습을 반복해 보세요. 특히 연결 속도를 느리게 사용하면 레이아웃 변경을 더 쉽게 식별할 수 있습니다. 또한 debugger 문을 사용하여 레이아웃 변경을 쉽게 단계별로 실행할 수 있습니다.

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      debugger;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

마지막으로, 개발 중에 재현할 수 없는 레이아웃 문제의 경우 선택한 프런트엔드 로깅 도구와 함께 Layout Instability API를 사용하여 이러한 문제에 관한 추가 정보를 수집하는 것이 좋습니다. 페이지에서 가장 크게 이동한 요소를 추적하는 방법에 관한 코드를 확인하세요.