개발 기술/사소하지만 놓치기 쉬운 개발 지식

[JS] 모달 창 띄울 때 레이아웃 깨짐 없이 스크롤 막는 법

by GicoMomg 2024. 6. 11.

1. 들어가기 전에…

  • 모달창(modal)은 사용자가 특정 작업을 수행하도록 유도하는 팝업창이다.
  • 모달은 창 이외의 영역을 흐리게 만들어 사용자가 창을 닫거나 작업을 완료하기 전까지 다른 부분을 조작할 수 없도록 만든다. 아래 gif는 부트스트랩의 모달 예시이다.

https://getbootstrap.kr/docs/5.0/components/modal/#모달-간-토글


  • 앞서 언급했듯이, 모달은 사용자가 특정 작업을 수행하도록 유도하는 역할을 한다.

  • 그런데 만약 모달이 열릴 때 창 이외 영역에서 스크롤이 가능하면 어떨까?

  • 아래 gif는 모달이 열려있을 때, 창 이외 영역에서 스크롤이 발생하는 예시이다.

  • 예시를 보면, 모달 이외의 영역으로 주의가 분산되기에 사용자 경험이 저하될 수 있다.


  • 그럼 어떻게 해야, 모달 이외의 영역에서 스크롤이 발생하는 걸 막을 수 있을까? 단순히 body 영역에 overflow: hidden을 적용하면 될까?



2. 외부 스크롤을 막는 방법

  • 외부 스크롤을 막는 가장 간단한 방법은, bodyoverflow: hidden 속성을 적용하는 것이다. 하지만 이것만으로 충분할까? 아니다.
  • 만약 스크롤바가 공간을 차지하는 경우 모달이 열릴 때마다 body의 너비가 변경된다.
  • 이 경우, 모달 이외의 영역으로 사용자의 시선이 옮겨지거나, body의 레이아웃이 깨질 수 있다!
  • 아래 gif는 모달 등장시 컨텐츠 박스의 배열이 변경되는 예시이다.


  • 모달이 뜨기 전에는 콘텐츠 레이아웃이 3x3이었으나, 모달 등장시 콘텐츠 레이아웃이 3x4로 변한다.
  • 이처럼 모달이 등장할 때, overflow: hidden만 적용하면 레이아웃이 변경되는 문제가 발생한다.


  • 그럼 어떻게 해야 레이아웃 깨짐 없이 스크롤을 막을 수 있을까? JS로 추가 처리하면 가능하다!

1) 결과 미리보기

💡 해당 포스팅을 다 읽고나면, 아래 gif처럼 스크롤 막기는 물론 body 영역의 너비가 변경되는 문제를 해결할 수 있다.


2) JS 코드 살펴보기

(1) 전체 코드 보기

  • 아래 코드는 레이아웃 깨짐을 방지하면서 스크롤을 막는 로직이다.
  • 레이아웃 깨짐을 방지하는 방법은 간단한데, 바로 overflow: hidden이 될 때 bodypaddingRight에 스크롤바 너비를 추가하는 것이다.
  • 방법도 알았으니, 이제 각 함수 별로 어떤 역할을 하는지 살펴보자 🙂
function getBodyScrollbarWidth() {
  return window.innerWidth - document.documentElement.offsetWidth;
}

function blockBodyScroll(className = 'overflow-hidden') {
  const isBlocked = document.body.classList.contains(className);
  if (isBlocked) return;

  document.body.style.setProperty('--scrollbar-width',  `${getBodyScrollbarWidth()}px`);
  document.body.classList.add(className);
}

function unblockBodyScroll(className = 'overflow-hidden') {
  const isBlocked = document.body.classList.contains(className);
  if (!isBlocked) return;

  document.body.style.removeProperty('--scrollbar-width');
  document.body.classList.remove(className);
}

(2) getBodyScrollbarWidth(), 스크롤바 너비 계산하기

  • 이 함수는 브라우저의 스크롤바 너비를 계산한다.
  • window.innerWidth는 창의 전체 너비를, document.documentElement.offsetWidth는 스크롤바를 제외한 너비를 나타낸다.


  • 이 둘의 차이를 이용하면 스크롤바 너비를 계산할 수 있다.
function getBodyScrollbarWidth() {
  return window.innerWidth - document.documentElement.offsetWidth;
}

(3) blockBodyScroll(), body의 스크롤 막기

  • 이 함수는 body 영역의 스크롤을 막는 역할을 한다.
  • 우선 스크롤 차단을 위해 overflow-hidden 클래스를 추가하고,
  • 스크롤바 너비를 계산하여 CSS 변수로 설정한다. 그리고 이미 차단된 경우에는 함수를 early return한다.
function blockBodyScroll(className = 'overflow-hidden') {
  const isBlocked = document.body.classList.contains(className);
  if (isBlocked) return;

  document.body.style.setProperty('--scrollbar-width', getBodyScrollbarWidth() + 'px');
  document.body.classList.add(className);
}

(4) unblockBodyScroll(), body의 스크롤 허용하기

  • 이 함수는 body 영역의 스크롤을 다시 허용하는 역할을 한다.
  • 스크롤 차단을 해제하기 위해 overflow-hidden 클래스를 제거하고, CSS 변수를 삭제한다.
  • 만약 이미 차단 해제된 경우에는 함수를 early return한다.
function unblockBodyScroll(className = 'overflow-hidden') {
  const isBlocked = document.body.classList.contains(className);
  if (!isBlocked) return;

  document.body.style.removeProperty('--scrollbar-width');
  document.body.classList.remove(className);
}



3) CSS 코드 살펴보기

  • 모달이 열릴 때, body에 overflow-hidden 클래스가 추가된다.
  • overflow-hidden 클래스에, overflow: hiddenpadding-right를 적용하자.
body.overflow-hidden {
  overflow: hidden; // 스크롤 막기
  padding-right: var(--scrollbar-width); // 스크롤바 너비를 우측 여백에 추가
}



4) 결과보기

  • 앞선 처리를 완료하면, 경우에 따라 body의 레이아웃을 유지하면서 스크롤을 막을 수 있다.
  • 아래 playground의 [스크롤 막기], [스크롤 허용하기] 버튼을 클릭해 테스트해보자 🙂

See the Pen block body scroll by KumJungMin (@kumjungmin) on CodePen.



3. 마치며…

  • 이번 시간에는 모달이 열릴 때 스크롤을 막으면서 레이아웃 깨짐을 방지하는 방법을 알아보았다.
  • 사소한 동작처럼 보이지만, 해당 처리를 통해 긍정적인 사용자 경험은 물론 레이아웃이 깨지는 현상을 막을 수 있다.
  • 대부분의 UI 라이브러리에서는 이를 기본적으로 제공하지만, 만약 자체 구현을 고려한다면 사용자 경험도 지켜주면 어떨까?
반응형

댓글