레이아웃 변경 디버그

레이아웃 변경을 식별하고 수정하는 방법을 알아봅니다.

Katie Hempenius
Katie Hempenius

이 도움말의 첫 번째 부분에서는 레이아웃 전환을 디버그하기 위한 도구를 설명하고, 두 번째 부분에서는 레이아웃 전환의 원인을 파악할 때 사용할 사고 과정을 설명합니다.

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는 기본 스레드가 유휴 상태가 될 때까지 기다렸다가 레이아웃 변경을 보고합니다. 따라서 기본 스레드의 사용량에 따라 레이아웃 전환이 발생한 시점과 콘솔에 로깅되는 시점 사이에 약간의 지연이 발생할 수 있습니다.
  • 이 스크립트는 사용자 입력 후 500밀리초 이내에 발생한 레이아웃 변경은 무시하므로 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

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

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

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

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 성능 패널의 환경 창에는 특정 성능 트레이스 중에 발생하는 모든 레이아웃 변경사항이 표시됩니다. 이러한 레이아웃 변경사항은 사용자 상호작용 후 500밀리초 이내에 발생하더라도 CLS에 반영되지 않습니다. 환경 패널에서 특정 레이아웃 변경 위로 마우스를 가져가면 영향을 받은 DOM 요소가 강조 표시됩니다.

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

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

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

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

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

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

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

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

DevTools에서 Layout Shift Regions를 사용 설정하려면 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를 원하는 프런트엔드 로깅 도구와 함께 사용하여 이러한 문제에 관한 자세한 정보를 수집하는 것이 좋습니다. 페이지에서 가장 많이 이동한 요소를 추적하는 방법에 관한 코드 예시를 확인하세요.