Dialog 태그로 모달 구현하기

se-een·2023년 4월 17일
4

Vanilla JS 노하우

목록 보기
2/2
post-thumbnail

모달은 보통 Div 태그로도 구현하지만 Dialog 태그로 구현하면 더 쉽고 간략하게 만들 수 있습니다. (리액트에서는 개인적으로 div를 권장합니다.)

좋은 UX를 위해 '닫기 버튼'으로 모달 닫기, 외에도 아래와 같은 기능을 추가해 보겠습니다.

  • ESC 키로 모달 닫기
  • 모달 외의 영역 클릭 시 모달 닫기
  • 모바일 사이즈에서는 모달을 하단에 고정시키기

급한 분들을 위해 먼저 코드와 결과물을 보면 다음과 같습니다.

(기본적으로 모바일 사이즈로 보이네요. 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(고급예제)에 잘 구현되어 있으니 확인해보시기 바랍니다.

ESC로 닫기

dialog 태그를 showModal()로 오픈했을 때 기본적으로 ESC를 눌러서 닫기 기능을 지원합니다.

참고로 dialog 태그를 열 수 있는 메서드는 두 가지가 있는데 바로 show()showModal()이죠.

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.targetevent.currentTarget을 비교하여 모달을 닫았지만 다음과 같은 로직도 가능합니다.

modal.addEventListener('click', (e) => {
  if (e.target.nodeName === 'DIALOG') modal.close();
});

dialog padding 제거

dialog 태그는 다음과 같이 기본적인 padding 값을 16px만큼 가지고 있습니다.

margin과 다르게 padding 값은 border 내부 즉, dialog 태그의 영역으로 감지되므로 모달 내부의 가장자리를 클릭해도 닫히는 불상사가 발생할 수 있습니다.

따라서 dialog 태그의 기본 padding 값을 제거하고, 바로 하위 div 태그에 padding: 16px을 설정하거나 제거하실 것을 권장합니다.

모바일 사이즈에서 하단 고정하기

들어가기 전에 잠깐! 왜 모바일 사이즈에서 모달을 하단에 위치하는 걸까요? 🤔

PC와는 다르게 모바일은 사람의 손가락이 마우스 역할을 합니다. 핸드폰을 잡고 있는 손의 위치가 보통 하단에 위치하게 되고, 이 상태에서 주로 엄지손가락으로 조작을 하게되는데 모달이 화면의 정중앙에 있다면 어떨까요?

모달과 인터렉션을 하기 위해 엄지손가락을 위로 끌어 올려야하므로 하단에 고정되어있는 모달보다 상대적으로 불편하겠죠? 엄지손가락을 위로 끌어 올리는 과정에서 오작동도 발생할 수 있습니다.

이런 이유에 의해서 모달을 하단에 고정하는게 아닐까 싶네요.

아무튼 모달을 하단으로 고정하기 위해 최대한 CSS를 활용해보았습니다. 모달을 띄우기 전 웹페이지에서 스크롤이 가능하냐, 불가능하냐에 따라서 조금 더 간단함의 차이는 있지만, 기본적인 원리는 다음과 같습니다.

모달을 showModal()로 오픈하면 자동적으로 정중앙에 위치하게 되는데 다음과 같이 position: fixedmargin: 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));으로 구현이 가능하니 둘 중 편하신 방법으로 사용하면 될 듯 싶습니다.

주의할 점 (display 속성)

dialog 태그에 모달 내부 요소의 CSS 작업을 위해 display 속성을 부여하면 기본적으로 모달이 숨겨지지 않습니다.

위 사진에서도 확인할 수 있듯이, 기본적으로 dialog 태그는 display: none 속성을 가지고 있어 모달 활성화 버튼을 누르기 전까지는 화면에서 보이지 않습니다.

하지만 display: flex 속성을 부여함으로써 기존의 display: none 속성이 무시되어 모달 활성화 버튼을 누르지 않았음에도 화면에 보이게 되는 것입니다.

저 또한 그랬고 흔히 dialog 태그에 display: flex 속성을 부여하는 실수를 하는데 '모달 외 영역을 클릭해서 닫기' 기능을 구현하기 위해서라도, dialog 바로 하위에 div와 같은 태그를 두시고 해당 태그에 display 속성을 부여하시길 바랍니다. 😀

profile
woowacourse 5th FE

7개의 댓글

comment-user-thumbnail
2023년 4월 17일

dialog에 show와 showModal의 차이점을 몰랐는데 글에서 알게 되었네요! 글 잘 보았습니다 😀

1개의 답글
comment-user-thumbnail
2023년 4월 18일

오호! 미션 진행할 때 유용한 꿀팁까지!!
레벨2에서도 파이팅입니다 세인~👍

1개의 답글
comment-user-thumbnail
2023년 4월 19일

크... 역시 세인짱짱맨~ 덕분에 dialog를 더 잘 쓸 수 있을 거 같아요!!

1개의 답글
comment-user-thumbnail
2024년 3월 1일

효자 포스트네요 ㅎㅎ

답글 달기