레이아웃 불안정성 수정

WebPageTest를 사용하여 레이아웃 불안정 문제를 식별하고 해결하는 방법을 둘러봅니다.

이전 게시물에서 WebPageTest의 누적 레이아웃 변경 측정 (CLS)에 관해 작성한 바 있습니다. CLS는 모든 레이아웃 변경을 집계한 것이므로 이 게시물에서는 페이지의 개별 레이아웃 변경을 자세히 살펴보고 검사하여 불안정의 원인이 될 수 있는 원인을 파악하고 실제로 문제를 해결하려고 노력하는 것이 흥미로울 수 있다고 생각했습니다.

레이아웃 변경 측정

Layout Instability API를 사용하여 페이지의 모든 레이아웃 변경 이벤트 목록을 가져올 수 있습니다.

new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(list.getEntries().filter(entry => !entry.hadRecentInput));
  }).observe({type: "layout-shift", buffered: true});
}).then(console.log);

그러면 입력 이벤트 이전에 발생하지 않은 레이아웃 변경의 배열이 생성됩니다.

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 210.78500000294298,
    "duration": 0,
    "value": 0.0001045969445437389,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

이 예에서는 210ms에서 0.01% 라는 매우 작은 이동 하나가 있었습니다.

이동 시간과 심각도를 알면 변동의 원인을 좁히는 데 도움이 됩니다. 더 많은 테스트를 실행하기 위해 실험실 환경에서 WebPageTest로 돌아가 보겠습니다.

WebPageTest에서 레이아웃 변경 측정

WebPageTest에서 CLS를 측정하는 것과 마찬가지로 개별 레이아웃 변경을 측정하는 데는 맞춤 측정항목이 필요합니다. 다행히 Chrome 77이 안정화 버전이므로 절차가 더 쉬워졌습니다. Layout Instability API는 기본적으로 사용 설정되어 있으므로 Chrome 77 내의 모든 웹사이트에서 JS 스니펫을 실행하고 즉시 결과를 얻을 수 있습니다. WebPageTest에서는 기본 Chrome 브라우저를 사용할 수 있으며 명령줄 플래그나 Canary 사용에 대해 걱정할 필요가 없습니다.

이제 이 스크립트를 수정하여 WebPageTest의 맞춤 측정항목을 생성해 보겠습니다.

[LayoutShifts]
return new Promise(resolve => {
  new PerformanceObserver(list => {
    resolve(JSON.stringify(list.getEntries().filter(entry => !entry.hadRecentInput)));
  }).observe({type: "layout-shift", buffered: true});
});

이 스크립트의 프로미스는 배열 자체가 아닌 배열의 JSON 표현으로 확인됩니다. 맞춤 측정항목은 문자열이나 숫자와 같은 원시 데이터 유형만 생성할 수 있기 때문입니다.

테스트에 사용할 웹사이트는 ismyhostfastyet.com입니다. 이 사이트는 웹 호스트의 실제 로드 성능을 비교하기 위해 구축했습니다.

레이아웃 불안정의 원인 파악

결과에서 LayoutShifts 맞춤 측정항목에 이 값이 있음을 알 수 있습니다.

[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3087.2349999990547,
    "duration": 0,
    "value": 0.3422101449275362,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

요약하면, 3087ms에 34.2% 의 단일 레이아웃 변경이 발생합니다. 원인을 파악할 수 있도록 WebPageTest의 필름 스트립 보기를 사용해 보겠습니다.

슬라이드의 두 셀이 레이아웃 변경 전과 후의 스크린샷을 보여줌
슬라이드에 레이아웃 변경 전후의 스크린샷이 표시된 두 개의 셀

슬라이드에서 약 3초 지점까지 스크롤하면 34% 레이아웃 변경의 원인이 정확히 무엇인지 알 수 있습니다. 바로 컬러풀한 표입니다. 웹사이트에서 JSON 파일을 비동기식으로 가져온 후 테이블로 렌더링합니다. 테이블은 처음에는 비어 있으므로 결과가 로드될 때 채워질 때까지 기다리는 것이 변경의 원인이 됩니다.

갑자기 나타나는 웹 글꼴 헤더
웹 글꼴 헤더가 갑자기 표시됨

그뿐만이 아닙니다. 페이지가 약 4.3초로 시각적으로 완성되면 'Is my host fast yet?' 페이지의 <h1>가 아무 곳에도 표시되지 않는 것을 볼 수 있습니다. 이는 사이트에서 웹 글꼴을 사용하고 있으며 렌더링을 최적화하기 위한 조치를 취하지 않았기 때문입니다. 이런 상황이 발생할 때 레이아웃이 실제로 바뀌는 것처럼 보이지는 않지만, 제목을 읽기 위해 너무 오래 기다려야 하면 사용자 환경이 저하됩니다.

레이아웃 불안정성 수정

비동기식으로 생성된 테이블로 인해 표시 영역의 3분의 1이 이동한다는 것을 알았으니 이제 문제를 해결할 차례입니다. JSON 결과가 실제로 로드될 때까지는 테이블의 내용을 알 수 없지만, 여전히 일종의 자리표시자 데이터로 테이블을 채울 수 있으므로 DOM이 렌더링될 때 레이아웃 자체가 비교적 안정적입니다.

다음은 자리표시자 데이터를 생성하는 코드입니다.

function getRandomFiller(maxLength) {
  var filler = '█';
  var len = Math.ceil(Math.random() * maxLength);
  return new Array(len).fill(filler).join('');
}

function getRandomDistribution() {
  var fast = Math.random();
  var avg = (1 - fast) * Math.random();
  var slow = 1 - (fast + avg);
  return [fast, avg, slow];
}

// Temporary placeholder data.
window.data = [];
for (var i = 0; i < 36; i++) {
  var [fast, avg, slow] = getRandomDistribution();
  window.data.push({
    platform: getRandomFiller(10),
    client: getRandomFiller(5),
    n: getRandomFiller(1),
    fast,
    avg,
    slow
  });
}
updateResultsTable(sortResults(window.data, 'fast'));

자리표시자 데이터는 정렬되기 전에 무작위로 생성됩니다. 여기에는 텍스트에 대한 시각적 자리표시자 및 세 가지 주요 값의 무작위로 생성된 분포를 만들기 위해 임의의 횟수만큼 반복되는 '█' 문자가 포함됩니다. 또한 데이터가 아직 완전히 로드되지 않았음을 명확히 하기 위해 표의 모든 색상의 채도를 낮추는 몇 가지 스타일을 추가했습니다.

사용하는 자리표시자의 모양은 레이아웃 안정성과 관련이 없습니다. 자리표시자의 목적은 사용자에게 콘텐츠가 시작되며 페이지가 손상되지 않았음을 확인하는 것입니다.

JSON 데이터가 로드되는 동안 자리표시자는 다음과 같이 표시됩니다.

데이터 표는 자리표시자 데이터로 렌더링됩니다.
데이터 테이블이 자리표시자 데이터로 렌더링됩니다.

웹 글꼴 문제를 해결하는 방법은 훨씬 간단합니다. 사이트에서 Google Fonts를 사용하고 있으므로 CSS 요청에서 display=swap 속성을 전달하기만 하면 됩니다. 여기까지입니다. Fonts API는 글꼴 선언에 font-display: swap 스타일을 추가하여 브라우저가 텍스트를 대체 글꼴로 즉시 렌더링할 수 있도록 합니다. 다음은 수정사항이 포함된 해당 마크업입니다.

<link href="https://fonts.googleapis.com/css?family=Chivo:900&display=swap" rel="stylesheet">

최적화 확인

WebPageTest를 통해 페이지를 다시 실행한 후 이전과 이후 비교를 생성하여 차이를 시각화하고 새로운 수준의 레이아웃 불안정성을 측정할 수 있습니다.

레이아웃 최적화 유무와 관계없이 두 사이트가 나란히 로드되는 모습을 보여주는 WebPageTest 슬라이드
레이아웃 최적화 여부에 따라 두 사이트가 나란히 로드되는 모습을 보여주는 WebPageTest 슬라이드
[
  {
    "name": "",
    "entryType": "layout-shift",
    "startTime": 3070.9349999997357,
    "duration": 0,
    "value": 0.000050272187989256116,
    "hadRecentInput": false,
    "lastInputTime": 0
  }
]

맞춤 측정항목에 따르면 여전히 3, 071밀리초 (이전과 거의 같은 시간)에 레이아웃 변경이 발생하지만 이동의 심각도는 0.005%로 훨씬 더 작습니다. 난 이걸 가지고 살아갈 수 있어.

또한 필름 스트립에서 <h1> 글꼴이 시스템 글꼴로 즉시 대체되어 사용자가 더 빨리 읽을 수 있다는 것이 명확합니다.

결론

복잡한 웹사이트에서는 이 예보다 훨씬 더 많은 레이아웃 변경이 발생할 수 있지만 해결 프로세스는 여전히 동일합니다. WebPageTest에 레이아웃 불안정성 측정항목을 추가하고 시각적 로드 필름스트립으로 결과를 교차 참조하여 원인을 파악하고 자리표시자를 사용하여 화면 공간을 예약하여 수정사항을 구현합니다.

(한 가지 더) 실제 사용자가 경험하는 레이아웃 불안정성 측정

최적화 전후에 페이지에서 WebPageTest를 실행하여 측정항목을 개선할 수 있다는 것은 좋지만, 정말 중요한 것은 사용자 환경이 실제로 개선되고 있다는 것입니다. 그렇기 때문에 Google은 애초에 사이트를 개선하고자 하는 것이 아닌가요?

따라서 기존 웹 성능 측정항목과 함께 실제 사용자의 레이아웃 불안정 환경을 측정하기 시작하면 얼마나 좋을까요? 이는 최적화 피드백 루프에서 중요한 부분입니다. 현장에서 수집된 데이터를 통해 문제가 어디에 있고 우리의 수정 사항이 긍정적인 변화를 가져왔는지 여부를 알 수 있기 때문입니다.

자체 레이아웃 불안정성 데이터를 수집하는 것 외에도 Chrome UX 보고서를 확인해 보세요. 이 보고서에는 수백만 개의 웹사이트에서 실제 사용자 경험으로부터 얻은 누적 레이아웃 변경 데이터가 포함되어 있습니다. 이를 통해 게시자 (또는 경쟁업체)의 실적을 확인하거나 웹에서 레이아웃 불안정성 상태를 탐색할 수 있습니다.