안녕하세요. 동쪽별입니다.
이번 글에서는 바닐라 자바스크립트로 모달을 직접 구현해본 경험을 공유하고자 합니다.
위 이미지와 같이 날짜 입력 아이템을 클릭시 캘린더 모달이 렌더링 되도록 구현했습니다.
모달은 position: absolute 스타일을 적용하여 쉽게 퍼블리싱할 수 있습니다.
그런다음 클릭 이벤트 발생시 모달 Element를 DOM에 추가하거나, display: none 이었던 모달 Element 스타일을 display: flex 로 변경하는 등의 방식으로 렌더링할 수 있겠죠.
문제는 모달 외 영역 클릭시 해당 모달을 제거하는 동작을 구현하는 것이었습니다.
또한, 모달 제거와 동시에 날짜 입력 아이템의 포커싱 또한 비활성화해야 했습니다.
첫번째 방법 : 상위 Element 에서 이벤트 전파
해당 페이지의 가장 상위 Element(#main-wrapper)에 이벤트를 전파하여, 검색바 내 입력 아이템(.main-search-item-wrapper) 클릭이 아닌 경우, 입력 아이템 및 모달을 비활성화했습니다.
오케이 됐다! 하고.. 캘린더 모달 내 버튼을 클릭하여 월을 바꾸는 이벤트를 발생시키니..?
모달이 닫혀버렸습니다. 😅
"검색바 내 입력 아이템(.main-search-item-wrapper) 클릭이 아닌 경우, 입력 아이템 및 모달을 비활성화"
위 문장처럼 검색바 내 입력 아이템 클릭이 아니기 때문입니다.
입력 아이템은 closest으로 확인이 가능하여 비활성화가 가능했지만, 모달 내 클릭 Element는 closest으로 확인이 불가능했습니다.
앞으로 여러 입력 아이템별 모달을 전부 구현시, 각 모달 내 클릭 Element가 많아질 것인데 이를 전부 어떻게 감지하여 비활성화 기능을 제어할 것인지 고민이 되었습니다.
1. 모달 내 클릭 Element에 공통 클래스명 또는 dataset을 넣기
모달이 하나도 아니고 여러개인데, 각 클릭 Element에 공통 클래스를 넣는다니..
생각만해도 비효율적이었습니다.
2. 모달 내 클릭 Element로부터 부모 Element를 재귀적으로 확인하기
부모 Element를 찾을 수 있다면, 각 모달의 공통 상위 Element만 확인하여 모달 비활성화를 제어할 수 있겠죠.
하지만 매번 DOM 재귀를 수행하기에 이 또한 비효율적이라 생각했습니다.
두번째 방법 : e.stopPropagation() 활용
이벤트 객체의 stopPropagtion() 메서드는 현재 이벤트 이후의 전파를 중지함으로, 이벤트 버블링을 막을 수 있습니다.
따라서 상위 Element(#main-wrapper)에 바인딩된 이벤트 핸들러가 동작하지 않기에, 모달 내 Element를 클릭하더라도 모달이 닫히지 않습니다.
드디어 방법을 찾았다! 하고서..
모달 내에서 일어나는 클릭 이벤트 핸들러마다 e.stopPropagion()을 적용하다보니 의문점이 들었습니다.
만약 현재 개발 중인 애플리케이션이 커지게 될 경우, 다수의 Element 모두가 모달 영역 외 클릭 이벤트 작업에 신경을 써야 하는 것인가..?
한 놈 때문에...!!!!!
세번째 방법 : 모달 Overlay 적용
모달 오버레이(Overlay)란 "모달 뒤에 투명 또는 반투명으로 배경을 가려주는 레이어"라 할 수 있습니다.
모달 뒤에 배경 Element(오버레이)를 덧대주고, 해당 Element에 클릭 이벤트 리스너를 달아준다면..?
→ 모든 클릭 Element들은 모달 비활성화 이벤트에 더이상 신경을 쓰지 않아도 됩니다! 🙌
모달 외 영역 클릭 이벤트 작업은 이제 오버레이에서만 제어하면 되는 것이죠.
따라서, 아래와 같이 DOM 구조를 변경했습니다.
주의사항 ❗️
모달이 렌더되면 오버레이가 깔리게 되는데, 그럼 모달 외 Element 클릭 이벤트 발생이 불가해집니다..
저는 모달이 열려있더라도 검색바를 클릭해야만 했습니다.
이땐 z-index 스타일을 적용시키면 됩니다!
모달이 열려있더라도 클릭 이벤트를 발생시켜야 하는 Element가 있다면, 해당 Element를 오버레이 Element보다 z-index를 높게하면 됩니다.
아래는 본인이 적용한 모달 스타일입니다.
#main-search-wrapper {
z-index: 2;
position: relative;
width: $search-width;
...
}
#main-search-modal-overlay {
z-index: 1;
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
display: flex;
justify-content: center;
align-items: center;
}
#main-search-modal-window {
position: absolute;
top: 200px;
width: $search-width;
display: flex;
justify-content: flex-end;
& > div {
border-radius: 40px;
background-color: map-get($color, white);
box-shadow: 0px 4px 10px rgba(51, 51, 51, 0.1);
}
}
.
.
.
모달 오버레이 뿐 아니라, 여러 방법 또한 존재할 것 같습니다. (ex. Backdrop)
혹시나 틀린 부분이나 추천하는 방법이 있다면 댓글 달아주시면 감사하겠습니다. 😁