원문: https://blog.sentry.io/your-background-images-might-be-causing-cls/
레이아웃 시프트(CLS)는 초기 콘텐츠가 로드되고 새로운 콘텐츠가 나타난 후 웹 페이지의 레이아웃이 예상치 못하게 변경되는 것입니다. 조금 불편한 정도면 다행이지만, 최악의 경우는 광고가 로드된 후 마우스 커서 아래에 갑자기 나타난 "지금 구매하기" 버튼을 실수로 클릭해 원하지 않은 구매가 발생하는 경우입니다.
CLS는 구글이 페이지에 점수(순위)를 매기는 핵심 웹 바이탈 중 하나입니다. 일반적으로 페이지가 처음 로드된 후 클라이언트 사이드의 데이터 페칭, 큰 이미지, 광고 또는 내장된 미디어 플레이어 로드에 의해 발생하며, 느린 인터넷 연결에 의해 더욱 악화됩니다. 성능 지표로 CLS를 사용하는 목적은 최고의 사용자 경험을 구축하고, 사용자가 의도하지 않은 작업을 수행하지 않기 위함입니다. 그러나 모든 CLS가 나쁜 것만은 아닙니다. 구글은 "실제로 많은 동적 웹 애플리케이션들은 페이지의 요소들 시작 위치를 자주 변경합니다. 레이아웃 변경은 사용자가 예측할 수 없는 경우에만 좋지 않습니다."라고 말합니다.
CLS는 소수점 이하 자릿수로 측정됩니다. 좋은 레이아웃 시프트 점수는 0.1 이하의 값입니다. 0.25보다 크면 좋지 않은 점수입니다. INP(Interaction to Next Paint) 측정 방법과 비슷하게, 구글은 사용자가 페이지에 있는 동안 예상치 못한 레이아웃 변경의 가장 큰 확장을 기준으로 페이지의 CLS를 평가합니다. 일회성 레이아웃 변경의 변화가 작은 경우에는 그 영향이 미미할 수 있습니다. 그러나 레이아웃이 여러 차례, 즉 누적되어 변경되는 것은 피해야 합니다.
웹 페이지에서 CLS가 발생하지 않도록 하는 검증된 방법은 다음과 같습니다.
width
와 height
속성을 포함하세요.@font-face: size-adjust
규칙을 사용하세요. Lazar Nikolov는 이 주제에 대해 그의 아티클 "웹 폰트와 공포의 레이아웃 시프트"에서 심층적으로 다룹니다.이 글은 CLS를 예방하기 위한 완벽한 가이드가 아니고, 당신이 모를 수도 있는 예외 케이스를 강조하기 위한 글입니다. 만약 관심이 있으시다면, Barry Pollard가 Smashing Magazine에서 CLS 문제를 해결하는 방법에 대해 자세하게 다루었으니 확인해 보세요.
크로미움 브라우저(예: 엣지, 브레이브, 크롬과 아크)에서 성능(Performance) 탭을 사용하여 레이아웃 시프트와 연관된 점수를 식별할 수 있습니다. 웹 사이트의 사용자에 따라 CPU 및 네트워크 쓰로틀링을 활성화하여 인터넷 연결 속도가 느려지고 컴퓨터 속도가 느려지는 것을 시뮬레이션할 수 있습니다.
새로고침 버튼을 클릭하여 페이지 로드를 기록하고 프로파일 생성을 기다리세요. 페이지 변화가 발생한다면, 레이아웃 시프트 줄이 표시됩니다. 레이아웃 시프트 이벤트를 확대하여 클릭하면 해당 이벤트의 누적 점수를 포함한 추가 상세 정보가 있는 요약 탭이 아래에 열립니다.
또한 크롬 브라우저의 새롭고 실험적인 도구인 "성능 인사이트(Performance Insights)"는 레이아웃 시프트를 더 빠르게 확인하도록 도와줍니다. 이 도구를 사용하려면, 개발 도구의 우측 상단에 있는 점 세 개의 메뉴를 클릭하고 "추가 도구(More tools)"를 마우스 오버한 뒤 "성능 인사이트(Performance Insight)"를 클릭합니다.
개발 도구에서 성능 인사이트 탭을 선택하고 "페이지 로드 측정(Measure page load)" 버튼을 클릭합니다. 이는 페이지 새로 고침과 로드시 발생하는 일을 기록합니다. 우측의 인사이트 패널 위에 관련된 점수와 함께 등록된 CLS가 표시됩니다. 이벤트를 클릭하면 HTML의 레이아웃 시프트의 소스를 포함하여 이벤트에 대한 자세한 내용을 확인할 수 있습니다.
이제 정말 본론으로 들어가보겠습니다. 우리는 보통 사용자가 상호작용할 수 있는 페이지에서 기대하지 않은 콘텐츠 변화하여 사용자 경험을 방해하는 CLS에 대해서만 말합니다. 결과적으로 저는 항상 눈에 보이는 콘텐츠 변화, 즉 실제 사용자 경험의 일부인 대화형 UI 요소만을 기반으로 계산된다고 생각했습니다.
그러나 최근에 CLS가 사용자의 UI 요소를 실제로 이동시키지 않은 배경 요소를 포함한 모든 페이지 요소에 대해 계산된다는 사실을 알게 되었습니다. CLS 계산기는 페이지 요소의 z-index를 고려할 만큼 충분히 지능적이지 않습니다.
제가 이걸 어떻게 발견했을까요? Sentry가 제 웹사이트에서 찾아줬습니다.
개발 단계에서 CLS를 확인하는 것은 좋은 방법이지만, 실제 사용자가 여러분의 웹사이트와 상호작용하면서 얻은 실제 데이터를 분석하는 것만큼 효과적인 방법은 없습니다. 최근에 저는 개인 웹 사이트의 성능과 핵심 웹 바이탈을 모니터링하기 위해 Sentry를 사용하기 시작했습니다.
트래픽의 50%에 대한 정보를 캡처하기 위해 Sentry를 실행했습니다. Sentry는 각각 캡처된 이벤트(또는 트랜잭션)에 대해 여러 관련된 태그를 전송하는데, 관련이 있다면 CLS의 원인이 될 수 있는 소스도 포함될 수 있습니다. 정말 유용한 점은 Sentry가 CLS의 원인을 가리키는 HTML 요소도 포함하고 있어 코드의 어느 부분을 수정해야 할지 정확히 알 수 있다는 것입니다.
CLS의 상위 요소를 찾으려면 Sentry를 열고 성능(Performance) > 웹 바이탈(Web Vitals)로 이동하세요. 최상위 성능 점수 및 점수 분석 아래에 모든 페이지 URL이 나열된 표가 표시됩니다. CLS 헤더를 클릭하여 점수를 내림차순으로 정렬하고 가장 낮은 점수를 찾습니다. 제가 선택한 날짜의 범위에서 가장 높은 CLS는 0.66이었습니다. 이는 해당 페이지의 사용자 중 75%가 경험한 가장 높은 CLS값인 p75 점수입니다.
표의 맨 위의 아이템을 클릭하세요. 특정 페이지(p75뿐만 아니라)에 대해 샘플링된 모든 이벤트의 개요를 볼 수 있습니다. 이벤트 테이블을 CLS 내림차순으로 다시 정렬하여 가장 높은 점수를 찾습니다. 저는 CLS가 0.92로 보고된 이벤트를 조사할 예정입니다.
이벤트 상세 페이지에서 아래로 스크롤하여 태그 세부정보 섹션을 보면 cls.source.1
의 태그 키가 표시됩니다. 마우스를 가져가면 캡처된 전체 HTML 요소 소스를 볼 수 있습니다.
지금부터 흥미로워집니다. 제 사이트의 이 페이지에서 CLS의 주요 소스는 main
요소에 연결된 CSS 가상 요소에서 나온 것이었고, 이 페이지에 약간의 디자인 요소를 제공하는 SVG가 포함되어 있었습니다. 제가 확신했던 것은 다음과 같습니다.
content
속성을 통해 페이지에 추가되었습니다.이게 어떻게 된 거죠?
실망스럽게도 저는 CSS를 통해 페이지에 추가된 SVG에 너비와 높이를 지정하지 않았다는 것을 알게 되었습니다. 부끄럽네요. SVG에 너비와 높이를 추가하여 저의 실수를 고치는 것은 CSS를 수정해야 함을 의미했습니다. 그리고 (나중에) 돌이켜보면, 변경 사항은 실제로 의미론적으로 더 정확한 CSS로 이어졌습니다.
다음은 변경 사항의 차이점이며, 변경 사항이 필요한 이유에 대해 살펴보겠습니다.
main::after {
- content: url("path-to.svg");
+ content: "";
+ background-image: url("path-to.svg");
+ background-repeat: no-repeat;
+ background-size: cover;
+ height: 0;
+ padding-bottom: calc(100% * 201 / 1280); /* 이미지의 종횡비 */
position: fixed;
bottom: 0;
left: 0;
width: 100%;
z-index: -1;
}
가상 요소 선택자는 CSS를 사용하여 요소 앞이나 뒤에 그려지는 박스와 같이 DOM 요소의 특정 부분의 스타일을 지정할 수 있습니다. 각 DOM 요소의 ::before
및 ::after
가상 요소에 대한 content
속성의 초기 값은 normal
로 설정되어 있으며, 이는 none
으로 계산됩니다. 이는 DOM이 사용되지 않을 수 있는 과도한 수의 박스를 그리는 것을 방지합니다. content
속성에 대한 공식 W3C 사양에서 더 자세히 읽을 수 있지만, 이해하기 어려운 내용입니다.
이 SVG는 페이지 콘텐츠를 흐름에서 중요하지 않은 디자인 세부 사항이었기 때문에, 코드 내 계층 구조를 문서화하기 위해 가상 요소를 사용하여 표시하기로 했습니다. 원래 이 SVG는 HTML main
요소의 ::after
가상 요소의 content
속성을 통해 페이지에 추가되었습니다.
원본 CSS를 수정하지 않고 SVG의 너비와 높이 속성을 추가하는 것은 이미지가 의도한 대로 페이지 전체 너비를 차지하지 않는 결과를 초래했습니다. 대신 CSS에서 width: 100%
를 적용하면, SVG의 너비가 주어진 너비 속성 크기(1280px)의 100%로 계산됩니다.
<svg
viewBox="0 0 1280 201"
width="1280"
height="201"
role="img"
xmlns="http://www.w3.org/2000/svg"
>
<!-- SVG 경로는 여기에 -->
</svg>
CSS에서 너비 속성을 사용하여 SVG를 컨테이너 너비의 100%로 확장하려면 CSS에서 SVG 요소를 직접 목표로 할 수 있어야 합니다. SVG가 가상 요소의 content
속성을 통해 추가된 경우, content 속성을 사용하여 삽입된 객체는 대체된 요소로 알려져 있으며 실제로 CSS의 범위를 벗어나게 되어 사용이 불가능했습니다. SVG 자체에 상대적인 너비 속성값을 100%로 추가하는 것도 CLS 문제를 해결하지 못했을 것입니다. 왜냐하면 브라우저는 그것을 그릴 공간을 계산하기 위해 절대 픽셀값을 필요로하기 때문입니다.
이 문제를 보면서, content
속성으로 인해 작업하기 어렵다면, 더 쉬운 방법이 있을 것으로 생각합니다. 그리고 여기서 시멘틱 CSS라는 개념에 대해 생각하기 시작했습니다.
content
속성의 사용은 이 배경 이미지의 의도와 모순되었습니다. 이것은 내용이 아니라 배경 이미지였습니다. 최종 구현에서는 content
속성을 빈 문자열로 설정하여 가상 요소를 그릴 수 있도록 하고, 디자인 의도대로 SVG를 배경 이미지로 설정하기 위해 background-image
속성을 사용했습니다.
padding-bottom 기법에 관해 설명을 덧붙이자면, CSS 배경 이미지에 대해 자동 크기 조정이 선택 사항이 아니기 때문에 뷰포트 전체 너비로 SVG를 늘릴 때, SVG 크기를 비례하여 조정하기 위해 필요했습니다. 원하는 결과를 얻기 위해 SVG의 너비를 100%, 높이를 auto로 설정하는 것은 불가능했습니다. SVG 스케일링에 대해 더 자세히 알고 싶다면, CSS Tricks의 Amelia의 글을 참고하세요.
이 문제를 이렇게 깊이 조사하는 것은 다소 사소한 일일 수도 있습니다. 그러나 CLS는 1000번의 작은 문제들로 인해 악화되기 쉬운 핵심 웹 바이탈 점수 중 하나입니다. 물론, 여기저기에서 발생하는 불가피한 작은 레이아웃 시프트는 어느 정도 수용이 가능합니다. 하지만 문제가 발생할 때마다 작은 문제들을 해결하는 데 집중할수록, 성능 문제의 누적 효과로 고통받을 가능성은 줄어듭니다. 이는 고칠 시간을 줄이고, 더 많은 시간을 구축하는 데 할애할 수 있다는 의미입니다.