스크롤바가 없는데 화면 오른쪽에 빈 여백이 생기는 이유

웹 앱의 화면 가장자리는 평소에는 눈에 잘 들어오지 않습니다. 콘텐츠와 배경이 자연스럽게 이어지면 굳이 볼 이유가 없기 때문입니다. 그런데 여러 화면을 확인하던 중 오른쪽 끝에 비어 보이는 세로 띠가 계속 눈에 걸렸습니다. 어떤 화면에서는 나타나고, 어떤 화면에서는 덜 보였습니다.

처음에는 흔한 가로 오버플로 문제라고 생각했습니다. `100vw`를 쓴 요소가 있거나, 고정 헤더의 너비 계산이 어긋났거나, 큰 카드가 뷰포트 밖으로 밀려난 상황을 의심했습니다. 관련 스타일을 검색했지만 오른쪽으로 튀어나가는 요소는 찾지 못했습니다.

여러 앱에서 같은 한 줄을 찾았습니다

문제는 페이지 컴포넌트가 아니라 전역 스타일에 있었습니다.

html {
scrollbar-gutter: stable;
}

일부 앱은 같은 설정을 ``의 유틸리티 클래스에도 한 번 더 넣고 있었습니다. 중복 선언 자체가 거터를 두 배로 만들지는 않지만, 설정의 출처를 두 군데로 늘려 원인을 찾기 어렵게 했습니다.

`scrollbar-gutter: stable`은 클래식 스크롤바가 나타났다가 사라질 때 콘텐츠 너비가 바뀌지 않도록 스크롤바 자리를 미리 확보합니다. 스크롤이 필요하지 않은 화면에서도 오른쪽에 같은 폭을 남깁니다. 반면 macOS에서 흔히 보는 오버레이 스크롤바는 콘텐츠 위에 표시되므로 거터를 만들지 않습니다. 같은 코드인데 운영체제나 스크롤바 설정에 따라 증상이 다르게 보인 이유였습니다.

여기서 한 번 잘못 고쳤습니다

처음에는 전역 `scrollbar-gutter` 선언을 제거했습니다. 오른쪽의 예약 공간이 사라지니 당장 화면은 꽉 차 보였고, 타입 검사와 빌드도 통과했습니다.

하지만 이 수정은 문제 하나를 없애면서 원래 막고 있던 문제를 다시 열어 둡니다. 내용이 짧은 화면에서 긴 화면으로 이동할 때 클래식 스크롤바가 새로 생기면 사용 가능한 너비가 줄어듭니다. 중앙 정렬한 콘텐츠, 헤더 버튼, 카드 그리드가 스크롤바 폭만큼 움직일 수 있습니다. 전역 스타일에 남아 있던 주석도 바로 그 흔들림을 막기 위해 거터를 예약한다고 설명하고 있었습니다.

빈 띠가 싫다는 이유만으로 거터를 없애는 것은 절반짜리 해결이었습니다.

공간은 유지하고 배경만 이어 붙였습니다

CSS Overflow 명세를 다시 확인했습니다. 스크롤바가 없는 거터는 패딩 배경의 연장으로 칠해집니다. 그렇다면 예약 공간을 없앨 필요가 없습니다. 루트 캔버스의 배경을 명시하면 됩니다.

제가 정리한 기본 형태는 다음과 같습니다.

html {
--app-header-height: calc(3.5rem + env(safe-area-inset-top));
--app-canvas: var(--background);

scrollbar-gutter: stable;
background: linear-gradient(
to bottom,
var(--surface) 0 calc(var(--app-header-height) - 1px),
var(--border) calc(var(--app-header-height) - 1px) var(--app-header-height),
var(--app-canvas) var(--app-header-height)
) fixed;
}

위쪽은 고정 헤더와 같은 `surface` 색으로 칠하고, 헤더 아래는 앱의 기본 캔버스 색으로 이어집니다. 스크롤바가 없을 때는 예약된 오른쪽 공간까지 이 배경이 보입니다. 스크롤바가 생기면 같은 자리를 스크롤바가 사용하므로 콘텐츠 너비는 달라지지 않습니다.

앱마다 본문 가장자리 색이 다르면 `--app-canvas`만 바꿉니다. 일반적인 업무 화면은 `var(--background)`, 한 단계 옅은 작업 캔버스를 쓰는 화면은 `var(--surface-muted)`가 맞았습니다. 거터 선언은 전역 CSS 한 곳에만 두고, ``의 중복 유틸리티 클래스는 제거하는 편이 추적하기 쉬웠습니다.

이 방식은 콘텐츠를 거터 안으로 늘리는 것이 아닙니다. 클래식 스크롤바는 물리적인 너비를 차지하므로, 콘텐츠를 항상 끝까지 쓰면서 스크롤바가 나타나도 아무것도 가리지 않게 만드는 것은 불가능합니다. 콘텐츠가 줄어들거나, 스크롤바가 콘텐츠를 덮거나, 자리를 미리 비워 두는 셋 중 하나를 선택해야 합니다. 저는 세 번째를 유지하고 빈 공간을 앱 배경으로 보이게 했습니다.

오른쪽의 흰 스크롤바는 다른 문제였습니다

거터를 살펴보다 보니 내부 사이드바 스크롤바는 어두운 테마에 맞게 꾸며져 있는데, 문서 전체의 스크롤바는 운영체제 기본 색으로 남은 화면도 있었습니다. 이것은 거터의 원인과는 별개입니다. 루트 스크롤바에도 `scrollbar-color`, `scrollbar-width`, `::-webkit-scrollbar-thumb` 스타일을 적용하면 같은 시각 언어로 맞출 수 있습니다.

이번 확인에서 실제로 추적한 것은 여러 프로젝트에 같은 전역 거터 설정이 있었고, 그 설정이 오른쪽 예약 공간의 원인이었다는 점입니다. 최종 스타일을 모든 운영체제의 클래식 스크롤바 설정과 실기기에서 비교한 단계까지는 아니므로, 배포 전에는 Windows의 Chromium과 Firefox에서도 확인할 예정입니다.

다음에 비슷한 띠를 보면 가로 오버플로부터 뒤지기 전에 루트의 `scrollbar-gutter`를 확인하려고 합니다. 그리고 그 선언에 이유가 적혀 있다면 먼저 지우기보다, 예약된 공간에 어떤 배경이 칠해지는지부터 보겠습니다.

참고:

CSS Overflow Module Level 3의 scrollbar-gutter 정의

MDN scrollbar-gutter