모달 안에서 스크롤하다 끝에 닿으면 뒤 페이지가 밀려 올라간 이유
문제 발생
모바일에서 모달의 목록을 스크롤하다 끝에 닿으면 뒤에 있는 페이지가 대신 스크롤됐습니다. 모달을 닫고 나면 엉뚱한 위치에 가 있었습니다.
목록 맨 위에서 더 당기면 브라우저의 당겨서 새로고침이 발동해 입력하던 내용이 날아가기도 했습니다.
원인 분석
스크롤 체이닝이 기본 동작입니다. MDN 설명대로 overscroll-behavior는 브라우저가 스크롤 영역의 경계에 도달했을 때 무엇을 할지 정하는 속성이고, 여기에는 안쪽 요소를 스크롤하다 경계에 닿으면 바깥 요소가 대신 스크롤되는 체이닝과 바운스·당겨서 새로고침 같은 브라우저 효과가 포함됩니다.
기본값 auto는 문서 설명 그대로 기본 오버플로 동작이 그대로 일어나고 이웃한 스크롤 영역으로 체이닝이 허용됩니다. 데스크톱에서는 잘 드러나지 않지만 모바일에서는 바로 체감됩니다.
contain은 이렇게 정의돼 있습니다 — 이 값이 설정된 요소 안에서는 기본 오버플로 동작(예: 바운스)이 유지되지만 이웃 스크롤 영역으로의 체이닝은 일어나지 않아 아래 요소가 스크롤되지 않으며, 당겨서 새로고침이나 가로 스와이프 내비게이션 같은 네이티브 브라우저 내비게이션이 비활성화됩니다.
해결 방안
- 스크롤되는 영역에
contain을 줍니다.
.modal-body {
max-height: 70vh;
overflow: auto;
overscroll-behavior: contain;
}-
축을 구분해 쓸 수 있습니다. 세로만 가두고 싶으면
overscroll-behavior-y: contain입니다 — 가로 스와이프로 뒤로 가기를 쓰는 사용자를 막지 않습니다. -
바운스까지 없애려면
none입니다. 문서 구분대로contain은 요소 안의 기본 효과를 유지하고,none은 그 효과까지 막습니다. 지도나 캔버스처럼 자체 제스처를 쓰는 영역에 어울립니다. -
body를 고정하는 우회를 대체합니다. 모달을 열 때position: fixed로 배경을 잠그는 방식은 스크롤 위치가 튀고 iOS에서 특히 지저분합니다. 이 속성 한 줄로 해결되는 경우가 많습니다. -
페이지 전체의 당겨서 새로고침을 막을 수도 있습니다. 문서 예시처럼
html { overscroll-behavior: none; }을 쓰면 됩니다 — 다만 사용자가 기대하는 기본 동작을 없애는 것이므로, 편집 화면처럼 실수로 데이터를 잃는 경우에만 씁니다. -
실기기에서 확인합니다. 데스크톱 브라우저의 모바일 에뮬레이션으로는 당겨서 새로고침과 바운스가 재현되지 않습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.