누적 레이아웃 이동(CLS), 읽던 줄이 밀려나는 이유
글을 읽는 도중 문단이 툭 내려가는 정도를 재는 지표가 CLS다.
이미지와 광고, 웹 글꼴이 왜 본문을 밀어내는지, 들어올 자리를 어떻게 미리 잡아 두는지 기초부터 정리한다.
이 글의 목차레이아웃이 움직인다는 것8
만든 페이지를 PageSpeed Insights나 Chrome의 Lighthouse로 검사하면 성능 점수 아래에 지표가 몇 줄 나온다.
그중 Cumulative Layout Shift 줄에 0.3 같은 숫자가 빨간색으로 찍혀 있으면 어디부터 손대야 할지 막막하다.
이 숫자는 페이지가 화면에 그려진 뒤 내용이 예고 없이 얼마나 움직였는지를 나타낸다.
우리말로는 누적 레이아웃 이동이고, 영어 머리글자를 따서 CLS라고 부른다.
Lighthouse 10에서는 성능 점수의 25%가 이 값에서 나온다.
휴대폰으로 글을 읽다 보면 위쪽 사진이 뒤늦게 뜨면서 읽던 줄이 아래로 밀려날 때가 있다.
CLS는 바로 그 움직임을 잰다.
레이아웃이 움직인다는 것
브라우저는 HTML을 받으면 요소마다 화면 어디에, 얼마나 크게 놓을지 계산한다.
이 배치 계산을 레이아웃이라고 한다.
그런데 브라우저는 모든 파일이 도착할 때까지 기다리지 않는다.
글자부터 그려 두고, 이미지와 글꼴, 광고처럼 따로 받아 오는 것은 도착하는 대로 채워 넣는다.
늦게 온 요소가 자리를 차지하면 그 아래에 이미 그려져 있던 내용이 그만큼 밀려난다.
이것이 레이아웃 이동이다.
이런 움직임 때문에 읽던 위치를 잃거나, 누르려던 것과 다른 링크나 버튼을 누르게 된다.
흔한 원인은 크게 세 갈래다.
크기를 알려 주지 않은 이미지, 무엇이 올지 미리 알 수 없는 광고와 임베드, 늦게 도착하는 웹 글꼴이다.
크기를 알려 주지 않은 이미지
브라우저는 그림 파일을 받아 보기 전에는 그 그림의 높이를 모른다.
크기 정보가 없으면 일단 높이를 0으로 두고 레이아웃을 끝낸다.
파일이 도착하면 그제야 높이가 생기고, 그 아래 문단이 전부 그만큼 내려간다.
막는 방법은 간단하다.
img 태그의 width와 height 속성에 원본 크기를 적어 두면, 브라우저가 파일이 오기 전에 가로세로 비율을 계산해 그만큼 자리를 비워 둔다.
반응형으로 만들다 보면 걱정이 하나 생긴다.
이미지 폭은 화면마다 달라지는데, 숫자를 적어 두면 크기가 어긋나지 않을까.
요즘 브라우저는 이 두 속성으로 이미지의 기본 비율을 정한다.
CSS에서 폭을 100%로, 높이를 auto로 두면 실제 폭에 그 비율을 곱해 높이를 계산한다.
그러니 속성에는 원본 크기를 그대로 적으면 된다.
파란 줄이 지금 읽던 줄이다. 이미지를 불러왔을 때 이 줄이 밀리는 거리는 이미지 자리를 예약했는지에 따라 다르다.
읽던 줄이 움직인 거리 0px
자리를 예약하지 않으면 이미지가 도착하는 순간 그 높이만큼 읽던 줄이 밀린다. 크기를 미리 잡아 두면 이미지가 채워져도 읽던 줄은 움직이지 않는다.
이미지 자리를 비워 두지 않았다. 이미지를 불러오기 전이다.
움직인 거리는 이 예시 화면 안에서 잰 값이다. 실제 CLS는 움직인 영역이 화면에서 차지하는 비율과 이동 거리를 함께 계산한다.
정작 만드는 사람은 이 밀림을 잘 보지 못한다.
테스트용 이미지는 개발자의 브라우저 캐시에 이미 들어 있을 때가 많다.
그래서 개발 중인 화면에서는 이미지가 바로 떠 자리가 처음부터 잡혀 보이지만, 처음 오는 방문자에게는 한참 늦게 도착한다.
광고와 임베드는 무엇이 올지 모른다
광고는 페이지가 뜬 뒤에 따로 요청해서 받아 온다.
어떤 광고가 어떤 크기로 올지는 응답이 와야 안다.
유튜브 동영상이나 지도를 끼워 넣는 임베드도 사정이 비슷하다.
그래서 광고를 받아 그리는 순간 이미 보이던 본문이 밀려날 수 있다.
화면 위쪽에 있는 광고 칸일수록 밀어낼 내용이 많으니 더 조심해야 한다.
크기를 정확히 모르니 광고 칸에는 최소 높이를 정해 두는 것이 보통이다.
가장 자주 오는 광고 크기에 맞춰 빈자리를 먼저 잡아 둔다.
광고가 오지 않았다고 그 칸을 접어 버리면 안 된다.
web.dev의 CLS 최적화 문서도 잡아 둔 자리를 없애는 것이 내용을 끼워 넣는 것만큼 레이아웃을 흔든다고 짚는다.
페이지가 뜬 뒤 본문 위에 끼어드는 공지 띠나 쿠키 동의 배너도 같은 일을 한다.
처음부터 자리를 비워 두거나, 본문을 밀지 않도록 화면 위에 겹쳐 띄우면 된다.
글꼴이 바뀌는 순간에도 줄이 흔들린다
웹 글꼴은 글꼴 파일을 내려받아야 쓸 수 있다.
그동안 브라우저는 대체 글꼴로 먼저 그려 두거나, 글자를 잠시 감춰 두었다가 웹 글꼴이 오면 바꿔 끼운다.
글자를 감춰 두어도 흔들리기는 마찬가지다.
보이지 않는 동안에도 대체 글꼴을 기준으로 자리를 잡아 두기 때문에, 웹 글꼴로 바뀌는 순간 글자 폭과 줄바꿈이 달라진다.
그래서 대체 글꼴의 크기와 줄 간격을 웹 글꼴에 가깝게 맞춰, 글꼴이 바뀌어도 줄바꿈이 그대로 남게 한다.
font-display: optional로 첫 레이아웃 때까지 오지 않은 웹 글꼴을 그 화면에서 아예 쓰지 않는 방법도 있다.
점수는 어떻게 매기나
이동 한 번의 점수는 두 비율을 곱해 매긴다.
움직인 요소가 화면에서 차지한 면적의 비율과, 움직인 거리가 화면의 긴 변에서 차지하는 비율이다.
web.dev의 CLS 문서에 실린 예에서는 화면 절반을 차지하던 요소가 화면 높이의 25%만큼 내려간다.
이동 전후를 합친 면적이 화면의 75%이니 이 이동의 점수는 0.75 × 0.25 = 0.1875다.
이동이 1초 안쪽 간격으로 이어지면 최대 5초짜리 묶음으로 더한다.
페이지가 열려 있는 동안 생긴 묶음 가운데 가장 큰 값이 그 페이지의 CLS가 되고, 0.1 이하면 좋음, 0.25를 넘으면 나쁨으로 본다.
누르거나 입력하고 0.5초 안에 생긴 이동은 계산에서 뺀다.
버튼을 눌러 펼친 메뉴가 아래를 미는 것은 사용자가 예상한 움직임이기 때문이다.
Lighthouse는 괜찮은데 실제 지표는 나쁠 때
Lighthouse로 재면 CLS가 거의 0인데, Search Console의 Core Web Vitals 보고서에서는 나쁨으로 나와 당황할 때가 있다.
어느 한쪽이 잘못 잰 것은 아니다.
Lighthouse 같은 실험실 도구는 대개 페이지를 한 번 불러오는 동안만 잰다.
반면 Chrome이 실제 방문자에게서 모은 데이터에는 페이지가 열려 있는 내내 생긴 이동이 들어간다.
스크롤을 내리다 지연 로딩된 이미지가 자리 없이 들어오면, 그 이동은 실험실 측정에 잡히지 않는다.
PageSpeed Insights는 위쪽에 실제 사용자 데이터를, 아래쪽에 Lighthouse 측정값을 보여 주므로 둘을 한 화면에서 나란히 비교할 수 있다.
검색 순위 이야기가 따라붙는 이유
Google은 로딩 속도와 반응성, 시각적 안정성을 재는 지표 세 가지를 묶어 Core Web Vitals라고 부른다.
CLS는 그중 시각적 안정성을 맡는다.
Google 검색은 2021년부터 이 지표를 순위 신호에 넣었다.
순위를 정할 때 보는 여러 신호 가운데 하나라서, 점수가 좋다고 검색 결과 맨 위에 오른다는 보장은 없다.
자리를 미리 잡을 때 따라오는 대가
| 미리 잡는 방법 | 막아 주는 이동 | 남는 문제 |
|---|---|---|
| 이미지에 가로세로 적기 | 이미지가 도착할 때의 밀림 | 적은 비율이 실제와 다르면 도착 뒤 조금 움직인다 |
| 광고 칸에 최소 높이 두기 | 광고가 붙을 때의 밀림 | 광고가 없으면 빈칸이 남고, 더 큰 광고는 일부를 민다 |
| 대체 글꼴 크기 맞추기 | 글꼴이 바뀔 때의 줄바꿈 변화 | 글꼴 조합마다 값을 따로 맞춰야 한다 |
| 늦게 온 글꼴은 쓰지 않기 | 화면이 뜬 뒤의 글꼴 교체 | 느린 연결에서는 웹 글꼴이 안 보일 수 있다 |
새 이미지를 넣을 때마다 크기를 적는 일은 사람이 잊기 쉽다.
빌드 단계에서 width나 height가 빠진 이미지를 찾아 실패시키는 검사를 두면 그 실수가 배포까지 가지 않는다.
점검은 PageSpeed Insights에서 시작하면 된다.
Lighthouse 진단에는 크기가 빠진 이미지와, 불러오는 동안 움직인 요소의 목록이 따로 나온다.
실제 사용자 지표만 나쁘다면 스크롤하며 들어오는 이미지와 광고부터 본다.