KKflow

BUG · 버그 도감 #16

작은 폰에서 '홈으로'에 닿지 않았다: 가운데 정렬이 위아래를 똑같이 자를 때

스택 타워의 결과 화면이 높이 640px 휴대폰에서 화면보다 길어져 점수와 '홈으로' 버튼이 잘렸습니다. 세로 가운데 정렬은 넘친 내용을 위아래로 똑같이 자르고, 위로 넘친 부분은 스크롤로도 닿을 수 없습니다. 원인을 브라우저에서 재현하고, 위부터 배치하고 auto 여백을 주는 해결을 정리했습니다.

· KKflow · 읽는 시간 약 8분

버그 도감은 KKflow 프로젝트에서 실제로 만난 버그를 한 편에 하나씩 기록하는 시리즈입니다. 모든 내용은 해당 저장소의 커밋 기록과 코드를 근거로 합니다.

프로젝트 스택 타워 · 시기 2026년 9월 6일 · 관련 기술 CSS flexbox, 스크롤 넘침, 터치 영역, 접근성

증상: 게임이 끝났는데 나갈 버튼이 없다

스택 타워는 블록을 한 층씩 쌓는 웹게임입니다. 한 판이 끝나면 결과 카드가 화면 가운데에 뜹니다. 카드에는 모드 이름, 큰 점수, 등급, 최고 기록·최대 콤보·코인이 있고, 그 아래로 '다시 하기', '랭킹 보기', '공유하기', '홈으로' 같은 버튼이 세로로 늘어서 있습니다. 상황에 따라 '다음 도전'이나 '이어하기' 버튼도 더 붙습니다.

2026년 9월 6일 새벽 2시 54분(한국 시각)에 합쳐진 커밋의 첫 줄이 이 화면의 문제를 적었습니다. 높이 640px짜리 화면에서 결과 화면과 대결 방 화면이 화면보다 길었다(765px). 그리고 그 결과는 이렇습니다. "가운데 정렬은 위아래를 똑같이 잘라 점수도 '홈으로'도 안 보이고, overflow를 켜도 위쪽은 스크롤로 못 간다."

게임을 끝낸 사람이 맨 먼저 보는 것이 점수이고, 맨 마지막에 누르는 것이 '홈으로'입니다. 작은 휴대폰에서는 그 둘이 동시에 잘려 있었습니다. 이 커밋은 Claude와 함께 작성했습니다(커밋에 Co-authored-by: Claude가 남아 있습니다).

어떻게 찾았나: 화면을 다섯 가지 크기로 열어 보기

이 버그는 플레이 중에 나온 것이 아니라 점검에서 나왔습니다. 커밋 설명에 따르면 이번 점검은 "모든 화면·모달을 5가지 크기와 두 언어로 열어 넘침·잘림·작은 탭·접근성을 자동으로 재고 스크린샷을 직접" 보는 방식이었습니다. 바로 앞의 점검이 플레이를 훑었으니 이번에는 화면을 본다는 설명도 붙어 있습니다.

데스크톱 브라우저나 세로가 긴 휴대폰에서는 카드가 화면 안에 들어가서 이 문제가 드러나지 않습니다. 360×640은 작은 안드로이드 폰에서 흔한 화면 크기이고, 큰 폰이라도 브라우저 주소창과 아래 툴바가 자리를 차지하면 세로가 그만큼 줄어듭니다. 크기를 바꿔 가며 일부러 열어 보지 않으면 만나기 어려운 버그입니다.

원인: 세로 가운데 정렬은 넘친 만큼을 반씩 자른다

스택 타워의 모든 화면은 같은 규칙을 씁니다(index.html의 .screen).

.screen{
  position:fixed; inset:0;
  display:flex; flex-direction:column;
  align-items:center; justify-content:center;
  gap:14px; padding:24px;
}

화면 전체를 덮는 상자 하나에 내용을 세로로 쌓고, justify-content:center로 세로 가운데에 놓습니다. 내용이 화면보다 작을 때는 위아래 빈 공간이 똑같이 나뉘어 보기 좋게 가운데에 옵니다. 문제는 내용이 화면보다 클 때입니다. 가운데 정렬은 그때도 "가운데"를 지킵니다. 남는 공간이 음수가 되면 그 음수를 위아래로 반씩 나눠서, 내용이 위로도 아래로도 똑같이 삐져나갑니다.

아래로 넘친 부분은 스크롤을 켜면 내려가서 볼 수 있습니다. 하지만 위로 넘친 부분은 다릅니다. 스크롤 상자는 맨 위(스크롤 위치 0)보다 더 위로 올라갈 수 없고, 가운데 정렬 때문에 위로 밀려난 내용은 그 0보다 위에 있습니다. 그래서 커밋 설명대로 "overflow를 켜도 위쪽은 스크롤로 못" 갑니다.

이 동작을 같은 규칙으로 직접 재현해 봤습니다. 높이 640px 화면에, 안쪽 여백 24px을 포함해 765px가 되는 상자를 넣고 Chromium에서 위치를 쟀습니다.

전: 가운데 정렬 스크롤 없음 점수 홈으로 위 38px 닿을 수 없음 아래 39px 보이지 않음 가운데 + 스크롤 켜기 아래만 스크롤됨 점수 홈으로 위 38px 닿을 수 없음 스크롤 후: 위부터 + auto 여백 끝까지 스크롤됨 점수 홈으로 스크롤 640px 화면, 안쪽 여백 24px, 높이 717px 상자(여백 포함 765px)로 Chromium에서 재현한 값
원리를 보여 주는 그림입니다. 숫자는 결과 화면과 같은 규칙(세로 flex, 안쪽 여백 24px)에 높이만 맞춘 상자를 브라우저에 띄워 잰 값이고, 실제 결과 카드의 측정값(홈으로 버튼 바닥 712px → 스크롤 후 587px)은 커밋 설명에 있습니다.
  • 가운데 정렬만: 상자 위쪽이 화면 위로 38px, 아래쪽이 화면 아래로 39px 나갑니다. 스크롤이 없으니 둘 다 볼 수 없습니다.
  • 가운데 정렬 + overflow-y:auto: 아래로는 63px 스크롤되어 끝까지 보입니다. 하지만 스크롤을 맨 위로 올려도 상자 위쪽은 여전히 −38px에서 시작합니다. 위 38px은 어떻게 해도 닿지 않습니다.
  • 위부터 배치 + auto 여백(커밋의 해결): 상자가 여백 24px 아래에서 시작하고, 125px 스크롤하면 끝까지 보입니다. 빠지는 곳이 없습니다.

실제 결과 카드에서 잰 값은 커밋 설명에 있습니다. 고치기 전 '홈으로' 버튼의 바닥이 712px로 화면(640px) 밖에 있었고, 고친 뒤에는 스크롤해서 587px 위치까지 올라옵니다.

해결: 넘치면 위에서부터, 작으면 가운데

고친 코드는 여섯 줄입니다. 커밋에는 왜 이렇게 했는지 주석이 함께 들어갔습니다.

/* 짧은 화면(640px)에서 결과 카드가 화면보다 길다. 가운데 정렬은 위아래를 똑같이
   잘라 '홈으로'가 닿지 않게 되므로, 넘치면 스크롤되고 작으면 가운데 오게 한다.
   (justify-content:center + overflow는 위쪽이 잘려 스크롤로도 못 간다) */
#result,#room{overflow-y:auto;justify-content:flex-start}
#result>:first-child,#room>:first-child{margin-top:auto}
#result>:last-child,#room>:last-child{margin-bottom:auto}

세 줄이 하는 일은 이렇습니다.

  1. 세로 정렬을 위에서부터(flex-start)로 바꾸고 스크롤을 켭니다. 이제 넘친 내용은 전부 아래쪽으로만 나가므로 스크롤로 모두 닿을 수 있습니다.
  2. 첫 자식의 위 여백을 auto로 둡니다.
  3. 마지막 자식의 아래 여백을 auto로 둡니다.

flexbox에서 margin:auto는 남는 공간을 그 여백이 가져가라는 뜻입니다. 내용이 화면보다 작으면 남는 공간이 위아래 auto 여백에 똑같이 나뉘어 결과적으로 가운데 정렬과 같아집니다. 내용이 화면보다 크면 남는 공간이 없으니 auto 여백은 0이 되고, 내용은 위에서부터 시작해 아래로 스크롤됩니다. 커밋 설명의 한 줄 요약 그대로 "작으면 가운데, 넘치면 스크롤"입니다. 큰 화면에서는 고치기 전과 똑같이 보이고, 작은 화면에서만 동작이 달라집니다.

참고로 CSS에는 이 상황을 위한 justify-content: safe center라는 값도 있습니다. 넘치지 않으면 가운데, 넘치면 시작 쪽에 붙입니다. 위 재현에서도 최신 Chromium에서는 auto 여백과 같은 결과가 나왔습니다. 이 커밋은 auto 여백 방식을 썼고, safe 값을 고려했는지는 기록에 없습니다. auto 여백은 flexbox를 지원하는 브라우저라면 어디서나 같은 방식으로 동작합니다.

같은 점검에서 나온 네 가지

같은 커밋에는 결과 카드 말고도 네 가지가 더 들어 있습니다. 모두 "작은 화면이나 손가락, 화면 낭독기로 쓰면 걸리는 것"입니다.

  • 모달 닫기 ✕가 18×25px이었습니다. 글자 크기는 그대로 두고 누를 수 있는 자리만 44×44px로 넓혔습니다. 코드 주석: "실측 18×25px는 엄지로 못 누른다."
  • 랭킹 탭이 가로 스크롤이라 마지막 탭('내 기록')이 잘려 있었는데, 잘렸다는 것조차 알기 어려웠습니다. 탭을 여러 줄로 감싸게(flex-wrap) 바꾸고 높이를 36px로 맞췄습니다.
  • 설정의 켜고 끄기 버튼에 이름이 없었습니다. 화면 낭독기로 들으면 그냥 "버튼"이었습니다. role="switch"와 aria-checked, 그리고 '사운드'·'진동' 같은 이름을 붙였고, 켜고 끌 때 aria-checked도 함께 바뀌게 했습니다.
  • 언어 버튼 높이가 27px이라 36px로 키웠습니다.

마지막으로 서비스 워커의 캐시 이름이 stack-tower-v45에서 v46으로 올라갔습니다. 스택 타워는 오프라인 실행을 위해 화면 파일을 캐시해 두는데, 캐시 이름을 바꿔야 이미 설치한 사람도 새 화면을 받습니다. 파일 첫머리 주석에 "배포로 index.html이 바뀌면 CACHE 이름의 버전을 올려주세요"라고 적혀 있습니다.

교훈

  • 세로 가운데 정렬은 넘칠 때 위를 잃는다. 내용 길이가 바뀌는 화면(결과, 설정, 모달)에 justify-content:center를 쓸 때는 화면보다 길어지는 경우를 먼저 생각해야 합니다. 첫·마지막 자식의 auto 여백이나 safe center로 "작으면 가운데, 넘치면 위부터"를 만들 수 있습니다.
  • 스크롤을 켜는 것만으로는 부족하다. 아래로 넘친 것은 스크롤로 해결되지만 위로 넘친 것은 해결되지 않습니다. 고쳤다고 생각한 뒤에도 맨 위가 보이는지 확인해야 합니다.
  • 작은 화면은 일부러 열어 본다. 이번 버그는 화면 크기 다섯 가지와 언어 두 가지(한국어·영어)를 돌려 본 점검에서 나왔습니다.
  • 숫자로 남긴다. "잘린다"가 아니라 "765px, 바닥 712px → 587px", "18×25px" 같은 실측값이 커밋에 남아 있어서, 나중에 다시 확인할 때 같은 기준으로 잴 수 있습니다.

스택 타워의 다른 버그로는 버그 도감 #14: 코드 리뷰 11건과 버그 도감 #10: 보이지 않는 도전 목표가 있고, 게임이 만들어진 과정은 스택 타워 제작기에 있습니다. 화면을 덮는 요소가 엉뚱한 곳에 열린 비슷한 CSS 버그는 버그 도감 #12에 정리했습니다.

함께 읽으면 좋은 글