modal(popup) 에서 외부(body or parentElement) 스크롤 막기
Disable Scrolling with JavaScript
햄버거 버튼을 눌렀을때 사이드에서 네비게이션 모달이 나오는 페이지의 형태는 쉽게 찾아볼 수 있다. 이때 사용성을 위해서 모달창은 스크롤이 가능하지만(화면보다 네비게이션의 높이가 긴 경우), 그 외에 dimmed 영역은 스크롤이나 터치 이벤트가 불가능하게 하는 것이 좋다.
disableScroll
아래의 코드는 간단하게 스크롤 이벤트를 막을 수 있지만 현재의 위치를 기억하지 못하고 Y의 위치가 0으로 이동해버린다는 단점이 있다.
function noScroll() {
window.scrollTo(0, 0);
}
// add listener to disable scroll
window.addEventListener('scroll', noScroll);
// Remove listener to re-enable scroll
window.removeEventListener('scroll', noScroll);
위의 코드를 보완하기 위해서는 현재의 scrollY 위치를 기억하게끔 해야한다. 방법은 아래와 같다.
function disableScroll() {
document.body.style.overflow = 'hidden';
document.querySelector('html').scrollTop = window.scrollY;
}
function enableScroll() {
document.body.style.overflow = null;
}
touchmove
const [showModal, setShowModal] = useState(false);
useEffect(() => {
function handleTouchMove(event) {
if (showModal) {
event.preventDefault();
}
}
window.addEventListener("touchmove", handleTouchMove, {
passive: false
});
return () =>
window.removeEventListener("touchmove", handleTouchMove);
}, [showModal]);
event.preventDefault()
를 호출하는 이유는 터치 이벤트의 기본 동작인 scroll
을 막기 위함이고, addEventListener
의 세번째 인자로 주어진 passive: false
는 touch 이벤트 발생시 preventDefault
가 호출되면 scroll을 막겠다는 의도이다. 반대로 passive: true
가 되면 preventDefault
함수를 무시하고 scroll을 하게 된다.
이렇게 이벤트를 막으면 발생하는 문제점, 바로 모달의 내부 스크롤까지 동작하지 않는다는 것이다. 이벤트는 부모 노드로부터 타겟노드로 전파(캡쳐링)되고 그 다음에 타겟 노드에서 부모 노드로 전파(버블링)되기 때문이다. 이때 stopPropagation
함수를 추가하면 더 이상 이벤트가 전파되지 않도록 막는다.
<ModalWrap onTouchMove={e => e.stopPropagation()} />
useEffect(() => {
function handleTouchMove(event) {
if (NavOn) { event.preventDefault(); }
}
function disableScroll() {
document.body.style.overflow = 'hidden';
document.querySelector('html').scrollTop = window.scrollY; // dimmed 되기 전 스크롤 위치 고정
}
window.addEventListener('touchmove', handleTouchMove, { passive: false })
window.addEventListener('scroll', disableScroll);
return () => {
window.removeEventListener('touchmove', handleTouchMove);
window.removeEventListener('scroll', disableScroll);
document.body.style.overflow = 'visible';
}
}, [NaveOn]);
간단하쥬~?