레이아웃 시프트
들어가며
웹 페이지를 읽고 있는데 버튼이 갑자기 아래로 밀려버려 클릭 위치가 바뀌거나, 텍스트가 화면에서 점프하듯 움직여 당황한 경험이 누구나 한 번쯤 있을 것이다.
이런 현상을 레이아웃 시프트(layout shift)라고 부르며, 사용자 경험을 해치는 대표적인 문제다.
이 글에서는 레이아웃 시프트가 무엇인지, 왜 발생하는지, 어떻게 방지할 수 있는지 간단하게 정리해보려 한다.
레이아웃 시프트 정의
레이아웃 시프트는 브라우저가 페이지를 렌더링하는 도중 예상치 못한 레이아웃 변경이 발생하는 현상이다. 사용자가 아무 행동도 하지 않았는데 콘텐츠 위치가 움직이면 레이아웃 시프트라고 할 수 있다.
구글은 이를 측정하기 위해 CLS(Cumulative Layout Shift)라는 지표를 제공한다. CLS는 누적 레이아웃 변화 값을 의미하며, 좋은 사용자 경험을 위해 권장 값은 0.1 이하이다.
왜 발생할까
레이아웃 시프트는 주로 다음과 같은 상황에서 발생한다.
- 이미지나 광고 영역에 고정된 크기가 없을 때
- 웹폰트가 늦게 로딩되어 텍스트가 갑자기 바뀔 때
- 비동기로 데이터를 받아오는 컴포넌트가 자리를 잡지 않고 등장할 때
- 버튼이나 UI 요소가 초기 상태에서 공간을 차지하지 않을 때
즉, 화면에서 위치가 변할 여지를 남긴 채 렌더링이 시작되면 발생한다.
어떻게 줄일 수 있을까
레이아웃 시프트를 해결하는 핵심은 페이지가 처음 렌더링되는 순간부터 최종 레이아웃을 예측 가능하게 만드는 것이다.
아래 방법들이 효과적이다.
1. 이미지 크기 지정
이미지 태그에 width와 height를 지정해 놓으면 브라우저가 공간을 미리 확보할 수 있다.
<img src="image.jpg" width="600" height="400" />
Next.js Image 컴포넌트가 기본적으로 이 문제를 해결해주는 이유다.
2. 미리 공간을 잡아두기
데이터 로딩이나 광고 슬롯처럼 나중에 내용이 들어올 요소는 단순 로딩 스피너 대신 일정한 높이의 박스를 먼저 렌더링한다.
<div style={{ height: "200px" }}>
{isLoading ? "..." : content}
</div>
3. 폰트 최적화
웹폰트가 늦게 로딩되면 글자가 깜빡이거나 줄 높이가 바뀐다. 이를 방지하려면 font-display 옵션을 적절히 사용한다.
예: swap, optional 등
4. 애니메이션 주의
레이아웃을 직접 변경하는 애니메이션보다는 transform 속성을 사용해 GPU 기반으로 처리한다.
마치며
레이아웃 시프트는 단순한 시각적 문제처럼 보이지만 사용자가 의도치 않은 곳을 클릭하게 만들고, 콘텐츠를 읽기 어렵게 만든다.
웹이 빠르게 느껴지는 경험은 단순히 속도만의 문제는 아니다. 화면이 안정적이고 예측 가능해야 진짜로 부드럽게 느껴진다.
앞으로 개발할 때 이미지 크기 정의, 로딩 상태 처리, 레이아웃 미리 잡기 같은 기본적인 규칙을 지키며 사용자 경험을 조금씩 더 다듬고자 한다.