요즘에 저는 사이드 프로젝트를 하고 있습니다. 사이드 프로젝트를 하면 뭔가 새로운 걸 해볼 수 있다는 것이 가장 좋은거 같습니다. 그 중에서 이번 시간에 다뤄볼 Modal Bottom Sheet도 새로운 경험이었습니다. 그 동안 안 해봤던 것이기 때문입니다.
요즘에는 모바일을 사람들이 많이 사용하기 때문에 모바일 웹 형태에 사이트를 만들고 있습니다. 그렇다보니 모바일 친화적인 UI를 구성하려는 시도를 하고 있는데요. 그 중에서 밑에서 위로 올라오는 팝업 형태 많이들 보셨을 겁니다.
저도 어떤 예시가 있을까 찾아보니 교보문고 카테고리 선택할 때도 있고 리디에서 작품 소개 볼 때도 있네요. 그만큼 많이 사용되는 UI인거 같습니다.
교보문고 처럼 단순한 팝업 형태도 있을 수 있고, 좀 더 나아가서 콴다처럼 터치 이벤트를 통해 자연스럽게 위로 올리고 내릴 수 있게도 구현할 수 있을 듯합니다. 하지만 이 때는 난이도가 상승하겠죠? ㅎㅎ
저는 일단 교보문고 스타일처럼 단순하게 가도록 하겠습니다. 기회가 된다면 콴다처럼 한 번 만들어보는 시도를 해보는 것도 좋을 거 같습니다. 아래는 저희가 만들려는 화면입니다. 필터를 선택했을 때 밑에서 위로 Modal Bottom Sheet가 짠~ 하고 나오도록 하는 것이 목표입니다.
일단 레이아웃을 잡을 때 검은 배경화면에 아래에는 흰색 배경의 콘텐츠 박스가 보여야 합니다. 흠... 일단 검은 배경화면부터 잡아볼까요? 여기서 주의해야 할 점은 스크롤을 좀 내린상태에서도 저 검은 배경화면은 꽉 차서 보여야 한다는 것입니다.
이 때 사용하면 좋은게 바로 fixed랑 inset-0인데요. inset의 의미는 사실 top, right, bottom, left의 축약형입니다. 즉, inset-0은 inset: 0과 같고, top-0, right-0, bottom-0, left-0과 같습니다.
<div className="fixed inset-0 bg-black/50 z-10">
결국 저 의미는 화면 전체를 다 덮으라는 말과 동일합니다. 그것도 fixed된 형태로요. 이제 무조건 검은 배경화면이 전체를 다 덮을 것입니다.
이제 흰색 Bottom Sheet을 보여줘야 하는데 이때는 간단하게 "absolute bottom-0 left-0" 을 사용했습니다.
<div className="absolute bottom-0 left-0 w-full max-w-screen-md h-[420px] bg-white rounded-t-lg px-4 pb-10" />
분명 저는 검은 배경에 화면을 꽉 찬 상태로 만들었고 당연히 뒤에 페이지는 스크롤이 안될거라 생각했습니다. 하지만... 뒤에가 너무 스크롤이 잘 되더군요. ㅎㅎ
몇가지 글을 찾아봤는데 이런 현상이 아마 일반적인거 같습니다. 해결책은 팝업을 띄울 때 직접 document.body.style에 overflow를 hidden으로 하고, 팝업이 없어지면 다시 스크롤이 되도록 할 수 있더군요. 이게 최선일까? 싶긴 하지만 잘 되긴 합니다.
혹시 더 좋은 방법이 있으면 댓글로 알려주시면 좋겠습니다..ㅎㅎ
useEffect(() => {
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "unset";
};
}, []);
저는 처음에 transform: translateY
+ transition
조합으로 간단하게 아래에서 위로 애니메이션이 가능할거라고 생각했습니다.
하지만 이렇게 해줬을 때 초기에 애니메이션이 작동하지 않습니다. 찾아보니 보통 저 조합으로 사용되는 예시가 :hover나 :active 같은 가상 셀렉터랑 사용하는게 일반적이더군요. 즉, 초기에 작동하는 애니메이션은 직접 animation을 사용해야 할 거 같습니다.
"일반적으로 트랜지션 효과는 요소 프로퍼티값이 다른 값으로 변화할 때 주로 사용하며 요소의 로드와 함께 자동으로 발동되지 않는다.”"
결국 tailwindcss에서 keyframs를 통해 직접 애니메이션을 추가 해줬습니다.
extend: {
keyframes: {
"bottom-sheet-up": {
"0%": { transform: "translateY(420px)" },
"100%": { transform: "translateY(0)" },
},
"bottom-sheet-down": {
"0%": { transform: "translateY(0)" },
"100%": { transform: "translateY(420px)" },
},
},
},
그리고 실제 적용할 때 중요한 점은 duration이 있어야 한다는 것입니다.
<div className="w-full max-w-screen-md h-[420px] bg-white rounded-t-lg px-4 pb-10 animate-[bottom-sheet-up_200ms_ease-in-out]">
일단 이렇게 해서 완성된 결과 입니다.