대니
danny's blog@dannywon_dev
발행 147 · 대기 171

1인 개발자. 클로드 코드를 팀처럼 굴린다. 매일 겪은 것만 쓴다.

← 버그와 함정

닫힌 모달이 body overflow hidden 을 지웠다

모달을 열었는데 뒤 배경이 스크롤됐다. 잠금 코드는 이미 있었고, 범인은 닫혀 있는 모달 둘이었다

모달을 열었는데 뒤 페이지가 그대로 스크롤되는 일이 있다. 코드를 열어보면 잠금이 빠져 있을 거라고 예상하는데, 이번엔 이미 있었다.

2026-08-05, 갤러리 관리 화면이었다.

if (isOpen) {
  document.body.style.overflow = "hidden";
} else {
  document.body.style.overflow = "";   // ← 여기
}

브라우저 콘솔로 재보니 모달이 열려 있는 상태인데 document.body.style.overflow 가 빈 문자열이었다. 누군가 지운 것이다.

겹쳐 뜬 모달인 줄 알았다

처음엔 겹쳐 뜬 모달 때문이라고 봤다. 안쪽 모달을 닫으면 바깥 모달이 아직 열려 있는데 해제가 돌아버리는, 흔한 카운터 문제다.

그런데 그날 화면엔 모달이 하나만 열려 있었다.

진짜 범인은 닫혀 있는 모달이었다. 그 화면엔 isOpen={false} 로 늘 마운트돼 있는 모달이 둘 있었다 — 앱 전역 확인창 루트, 그리고 폼 안에 미리 심어 둔 보조 모달. 부모가 데이터를 받아 렌더되는 순간 그것들의 else 분기가 실행되고, overflow = "" 가 열려 있는 모달 잠금까지 지웠다.

「닫힘」 분기가 곧 「해제」인 게 문제였다. 자기가 잠근 적도 없는데 해제부터 한다.

원인이 하나 더 있었다. document.scrollingElement 를 찍어 보니 html 이었다. 스크롤 주체가 html 인 레이아웃에서는 body{overflow:hidden} 이 아무 일도 안 한다.

열린 개수를 세는 모듈로 옮겼다

잠금을 컴포넌트에서 떼어내 모듈 하나로 옮겼다.

let lockCount = 0;

export const lockPageScroll = () => {
  if (++lockCount > 1) return;         // 이미 누가 잠갔다
  document.body.style.overflow = "hidden";
  document.documentElement.style.overflow = "hidden";
};

export const unlockPageScroll = () => {
  if (lockCount === 0 || --lockCount > 0) return;   // 아직 열린 게 남았다
  // 되돌리기
};

documentElement 도 같이 잠근다. 쓰는 쪽은 열렸을 때만 건드린다. cleanup 이 곧 해제라 언마운트도 같이 처리된다.

useEffect(() => {
  if (!isOpen) return;
  lockPageScroll();
  return unlockPageScroll;
}, [isOpen]);

if (!isOpen) return; 한 줄이 핵심이다. 닫혀 있는 모달은 아무것도 하지 않는다.

마지막에 한 번 더 속았다

고치고 나서 자동화 도구로 스크롤해 봤는데 여전히 스크롤됐다.

overflow: hidden 은 사용자 스크롤만 막는다. scrollTop = n 같은 프로그램적 스크롤은 그대로 된다 — 스펙이 그렇다. 자동화 도구 스크롤이 후자라, 고쳐도 안 고쳐진 것처럼 보인다.

getComputedStyle 로 hidden 인지 확인하는 것까지가 도구가 할 수 있는 전부고, 실제 휠은 사람이 굴려 봐야 한다.

← 목록으로