모달을 열었더니 뒤에 있던 페이지가 오른쪽으로 살짝 덜컥 움직였다. 처음엔 애니메이션 버그인 줄 알았는데, 원인은 스크롤바였다.
모달 열 때 배경 스크롤을 막으려고 body에 overflow: hidden을 준다. 그 순간 세로 스크롤바가 사라지고, 스크롤바가 차지하던 폭(보통 15~17px)만큼 콘텐츠 영역이 넓어지면서 전체 레이아웃이 오른쪽으로 밀린다. 반대로 짧은 페이지에서 긴 페이지로 이동할 때도 스크롤바가 새로 생기면서 똑같이 콘텐츠가 왼쪽으로 밀린다.
예전엔 이걸 잡으려고 모달 열 때 body에 padding-right를 스크롤바 폭만큼 넣어주는 꼼수를 썼다. 스크롤바 폭을 JS로 재야 하고, OS마다 값이 달라서 신경 쓸 게 많았다.
scrollbar-gutter: stable
이제는 CSS 한 줄로 끝난다. 스크롤바가 들어갈 자리를 항상 미리 비워두는 속성이다.
html {
scrollbar-gutter: stable;
}
stable을 주면 스크롤바가 있든 없든 그 폭만큼 공간을 늘 확보해둔다. 스크롤바가 사라져도 자리는 그대로 남으니까, overflow: hidden을 걸어도 콘텐츠가 움직이지 않는다. 모달을 아무리 열고 닫아도 화면이 흔들리지 않는다.
좌우 대칭이 필요하면 both-edges를 쓴다. 스크롤바 반대쪽에도 같은 폭의 여백을 만들어줘서, 콘텐츠가 화면 정중앙에 놓인다.
.panel {
overflow-y: auto;
scrollbar-gutter: stable both-edges;
}
어디에 걸어야 하나
주의할 점 하나. 이 속성은 스크롤 컨테이너에 걸어야 동작한다. 페이지 전체라면 html에, 내부에 따로 스크롤되는 영역이 있으면 그 요소에 직접 준다. overflow: visible인 요소에 걸면 아무 일도 일어나지 않는다.
그리고 stable은 auto나 scroll처럼 실제로 스크롤이 가능한 요소에서만 의미가 있다. 사이드바나 채팅 목록처럼 내용이 늘었다 줄었다 하는 패널에 걸어두면, 스크롤바가 나타나고 사라질 때마다 안쪽 콘텐츠가 들썩이는 걸 막을 수 있다.
정리
브라우저 지원도 이제 안심해도 된다. Chrome·Edge·Firefox는 진작 됐고, Safari도 18.2부터 들어와서 사실상 다 커버된다.
레이아웃 시프트는 CLS 점수에도 잡히는 실제 UX 문제다. body { padding-right } 꼼수 쓰고 있었다면 지우고, 스크롤 컨테이너에 scrollbar-gutter: stable 한 줄 넣는 걸로 바꾸자. JS 없이 CSS만으로 깔끔하게 해결된다.