모달은 보통 Div
태그로도 구현하지만 Dialog
태그로 구현하면 더 쉽고 간략하게 만들 수 있습니다. (리액트에서는 개인적으로 div를 권장합니다.)
좋은 UX를 위해 '닫기 버튼'으로 모달 닫기, 외에도 아래와 같은 기능을 추가해 보겠습니다.
급한 분들을 위해 먼저 코드와 결과물을 보면 다음과 같습니다.
(기본적으로 모바일 사이즈로 보이네요. 0.5x로 설정하시면 PC 사이즈의 모달을 보실 수 있습니다.)
모달 열기는 간단하므로 위 코드에서 설명을 대체하겠습니다. show()
와 showModal()
의 차이는 아래에 정리해두었습니다.
닫기 버튼은 모달 내부의 버튼에 클릭 이벤트 리스너를 달아준 뒤 modal.close()
를 해줘도 되지만, 아래와 같이 html 태그로도 충분히 가능합니다.
<form method="dialog">
<button>close</button>
</form>
form 태그의 method 속성을 dialog로 지정하면 form 태그 내부의 버튼은 기본적으로 모달을 닫는 기능을 합니다.
또한 select 태그의 change 이벤트를 감지하여 option 태그의 값을 읽어드리거나, button 태그의 value 속성을 읽어드릴 수 있습니다.
자세한 예시는 MDN(고급예제)에 잘 구현되어 있으니 확인해보시기 바랍니다.
dialog 태그를 showModal()
로 오픈했을 때 기본적으로 ESC
를 눌러서 닫기 기능을 지원합니다.
참고로 dialog 태그를 열 수 있는 메서드는 두 가지가 있는데 바로 show()
와 showModal()
이죠.
이 둘의 차이점은 사용자의 행동을 제한하는가로 볼 수 있겠습니다. 다음 사이트에서 show -> showModal
순으로 열어보시고, 그 역순으로도 열어보시면 무슨 차이인지 쉽게 이해하실 수 있으실 겁니다.
show()
로 모달을 열었을 때 '절대적인' 위치에 열린다고 설명하는데, 이는 position: absolute
속성을 갖고 있어서 그렇게 표현하는 듯 싶습니다. 🤔
위 페이지에서 보셨듯이 show()
로 모달을 열면 '모달 열기 버튼' 바로 하단 위치에 뷰포트 기준 정중앙에 고정되기에, 이를 잘 조절한다면 학습로그의 다음과 같은 기능으로도 응용 가능할 듯 싶습니다.
그리고 ESC 키를 누르면 닫기 버튼에 포커스되어 Enter
또는 SpaceBar
를 한 번 더 눌러줘야하는 번거로움이 있습니다.
그렇지만 showModal()
은 사용자의 행동을 제한하는 반면, show()
는 사용자의 행동을 제한하지 않으므로 모달이 열려있는 채로 추가적인 동작이 가능합니다. 이는 상황에 맞게 사용하시면 될 듯 합니다.
핵심은 event.target
이 dialog 태그이면 모달을 닫는 것인데요.
dialog 태그의 영역은 모달 영역 외에도 모달의 backdrop
까지 dialog 태그의 영역입니다.
따라서 dialog 태그 바로 하위에 div 태그를 두어 모달 내부 영역은 div 태그로 덮어버림으로써, event.target
이 dialog 태그일 때 모달을 닫게 하는 원리입니다.
위 코드에서는 event.target
과 event.currentTarget
을 비교하여 모달을 닫았지만 다음과 같은 로직도 가능합니다.
modal.addEventListener('click', (e) => {
if (e.target.nodeName === 'DIALOG') modal.close();
});
dialog 태그는 다음과 같이 기본적인 padding 값을 16px
만큼 가지고 있습니다.
margin과 다르게 padding 값은 border 내부 즉, dialog 태그의 영역으로 감지되므로 모달 내부의 가장자리를 클릭해도 닫히는 불상사가 발생할 수 있습니다.
따라서 dialog 태그의 기본 padding 값을 제거하고, 바로 하위 div 태그에 padding: 16px
을 설정하거나 제거하실 것을 권장합니다.
들어가기 전에 잠깐! 왜 모바일 사이즈에서 모달을 하단에 위치하는 걸까요? 🤔
PC와는 다르게 모바일은 사람의 손가락이 마우스 역할을 합니다. 핸드폰을 잡고 있는 손의 위치가 보통 하단에 위치하게 되고, 이 상태에서 주로 엄지손가락으로 조작을 하게되는데 모달이 화면의 정중앙에 있다면 어떨까요?
모달과 인터렉션을 하기 위해 엄지손가락을 위로 끌어 올려야하므로 하단에 고정되어있는 모달보다 상대적으로 불편하겠죠? 엄지손가락을 위로 끌어 올리는 과정에서 오작동도 발생할 수 있습니다.
이런 이유에 의해서 모달을 하단에 고정하는게 아닐까 싶네요.
아무튼 모달을 하단으로 고정하기 위해 최대한 CSS를 활용해보았습니다. 모달을 띄우기 전 웹페이지에서 스크롤이 가능하냐, 불가능하냐에 따라서 조금 더 간단함의 차이는 있지만, 기본적인 원리는 다음과 같습니다.
모달을 showModal()
로 오픈하면 자동적으로 정중앙에 위치하게 되는데 다음과 같이 position: fixed
와 margin: auto
가 있기에 가능한 것입니다.
따라서 모바일 사이즈 뷰포트의 높이값에서 모달의 높이 값을 뺀만큼 margin-top
으로 지정해주면 뷰포트 하단 끝으로 위치시킬 수 있습니다.
그에 대한 연산과정이 margin-top: calc(100vh - var(--modal-height));
인 셈이죠.
모달을 열기 전 뷰포트가 스크롤이 가능하다면 더 쉽게 구현이 가능한데요. 위의 연산 속성 대신 position: sticky
를 지정해주면 됩니다.
스크롤이 가능한 뷰포트와 불가능한 뷰포트에서 차이를 비교하면 다음과 같습니다.
그 이유는 sticky
속성의 원리에 있는데요. 간단하게 요약하면 sticky 영역의 x 또는 y 위치값이 설정한 위치에 도달하기 전까지는 static
으로 도달 이후에는 fixed
처럼 행동하게 됩니다.
자세한 설명은 MDN(Position)에서 확인해보시길 바랍니다.
스크롤이 가능한 뷰포트에서도 margin-top: calc(100vh - var(--modal-height));
으로 구현이 가능하니 둘 중 편하신 방법으로 사용하면 될 듯 싶습니다.
dialog 태그에 모달 내부 요소의 CSS 작업을 위해 display
속성을 부여하면 기본적으로 모달이 숨겨지지 않습니다.
위 사진에서도 확인할 수 있듯이, 기본적으로 dialog 태그는 display: none
속성을 가지고 있어 모달 활성화 버튼을 누르기 전까지는 화면에서 보이지 않습니다.
하지만 display: flex
속성을 부여함으로써 기존의 display: none
속성이 무시되어 모달 활성화 버튼을 누르지 않았음에도 화면에 보이게 되는 것입니다.
저 또한 그랬고 흔히 dialog 태그에 display: flex
속성을 부여하는 실수를 하는데 '모달 외 영역을 클릭해서 닫기' 기능을 구현하기 위해서라도, dialog 바로 하위에 div와 같은 태그를 두시고 해당 태그에 display
속성을 부여하시길 바랍니다. 😀
dialog에 show와 showModal의 차이점을 몰랐는데 글에서 알게 되었네요! 글 잘 보았습니다 😀