아티클

따끈따끈한 HTML5의 <dialog> 요소를 지금 사용해보세요.

2022-12-02 17:58:28

안녕하세요, 엔비전스 입니다.

2022년 상반기, 해외 커뮤니티 웹 개발자 사이에서 <dialog>\(HTMLDialogElement)에 대한 이야기가 많이 나왔습니다. 그 이유는 바로, 모든 메이저 브라우저에서 이제 <dialog> 태그를 쓸 수 있게 되었기 때문입니다. 5월 12일부터 열린 Google I/O 2022의 What’s new for the Web Platform 세션에서 <dialog>에 대한 지원 소식을 전했습니다.

일찍이 2021년부터 Chromium 웹브라우저(Opera, Microsoft Edge, Samsung Internet)는 <dialog> 요소가 구현되어 사용이 가능했었으나, Quantum Engine을 사용하는 FireFox와 Webkit을 사용하는 Safari는 지원이 늦었습니다. 지난 글에서 커스텀 대화상자에 대한 VoiceOver의 performEscape 동작 지원에 대한 “What’s new in Web Accessibility” 세션 발표와 함께 Apple에서도 <dialog> 요소의 지원을 공식화했습니다.

그리고, 2022년 하반기, 드디어 FireFox와 Safari에서 <dialog>를 지원하게 되어, 호환성을 신경쓰지 않고, 기존에 불안정한 커스텀 대화상자를 버리고 <dialog>태그를 쓸 수 있게 되었습니다.

그래봤자 디자인 수정 요청 때문에 커스텀 요소 쓰지 않을까요?

아닙니다. <dialog>태그의 경우, CSS를 통한 스타일 수정이 제한되지 않았습니다. 이보다 기쁜 소식은 없죠. 대화상자를 위한 선택자로 커스터마이징할 수 있습니다.

어떻게 사용하나요?

기초적인 Javascript 지식, DOM 객체 사용법, CSS만 사용할 줄 알면, 누구나 쓸 수 있습니다. 어렵지 않아요.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Dialog Example</title>
  <style>
    :modal { /*:modal은 모달 상태의 대화상자 박스에 대한 선택자이다. 비모달 대화상자는 dialog 태그 선택자로 정의가 가능하다. */
      border:none;
      padding: 0;
    }
    :modal form { /* 모달 안에 있는 서식 컨테이너를 grid로 만들겁니다. 3개의 행을 가지며, 각 행은, header, body, bottom 영역으로 나뉩니다.*/
      display: grid;
      grid-template-rows: auto 1fr auto;
    }
    :modal form>*{ /* 서식 컨테이너 바로 아래에 있을 각 row에 padding 여백을 주어서 보기 시원한 디자인을 만듭니다. */
      box-sizing: border-box;
      padding:0.5em;
    }

    :modal .header { /* header에는 display를 flex로 주고, 안에 있는 모든 내용이 옆으로 달라붙도록 합니다. */
      display: flex;
    }
    :modal .header>.tit {
      flex:1; /*class="tit"은 타이틀 텍스트 요소입니다. flex:1;을 주면, 이 요소가 주변 요소가 있는 자리를 침범하지 않고 .header의 대부분을 차지합니다. */
    }
    :modal .body {
      min-height:150px; /* 최소 세로 넓이를 150픽셀로 줘서 너무 작은 대화상자가 나오는 것을 방지합니다. */
    }

    :modal .bottom {
      display: flex; justify-content: end; /* bottom 영역 역시 display:flex;를 줘서 가로로 정렬되게 합니다. bottom에는 대화상자 내에 있는 버튼을 넣을겁니다. 오른쪽에 버튼이 나타나길 원하니, justify-content를 end로 줍니다.*/
    }

    :modal button { /* 버튼 스타일을 무력화합니다. */
      appearance:none; border:none; background-color: transparent;
      font-weight: bold;
    }
    :modal #negative.default { /* 부정적인 답변에 대한 버튼이 .default이면 부정답변 버튼의 텍스트를 빨강색으로 표시 */
      color: red;
    }

    :modal #postive.default { /* 긍정적인 답변에 대한 버튼이 .default면 긍정 답변 버튼의 텍스트를 파란색으로 표시*/
      color: #2264ff;
    }
  </style>
</head>
<body>
  <button id="btnOpen" aria-haspopup="dialog">Open Greeting Dialog.</button>
  <p id="message" aria-live="assertive"></p> <!--결과값을 여기다가 표시할 겁니다.-->
  <dialog id="myDialog" aria-labelledby="wyn_title" aria-describedby="wyn_desc"> <!--대화상자를 만듭니다. 대화상자에 스크린리더가 접근하면, 대화상자 타이틀과 메시지를 안내하도록 aria-labelledby와 describedby를 활용해 줍니다.-->
    <form method="dialog"> <!--form의 새로운 method, dialog값은 대화상자 내에서 form이 전송되었을 때에 대한 동작을 얘기합니다.-->
      <div class="header"><strong id="wyn_title" class="tit">What's your name?</strong><button id="btnClose" aria-label="close" value="cancel">X</button></div> <!--제목 표시줄과 닫기 버튼입니다.-->
      <div class="body">
        <p id="wyn_desc">Hello, Nice to meet you, I'm a cool native dialog for everyone. I wanna know your name, could you tell me your name?</p> <!--대화상자 내부에 표시될 질문 내용입니다.-->
        <div>
          <label for="typeName">
            My name is
            <input type="text" name="typeName" id="typeName" autofocus>. <!-- 이름을 입력할 텍스트 입력란을 넣어줍니다. 대화상자가 나타나면 바로 입력할 수 있도록, autofocus를 걸어줍니다. -->
          </label>
        </div>
      </div>
      <div class="bottom">
        <button id="negative" name="negative" type="submit" value="negative">Answer Later</button> <!-- 부정버튼과 긍정버튼을 만듭니다. value값은 해당 버튼이 눌려 대화상자가 닫혔을 때 반환되는 값입니다.-->
        <button class="default" name="postive" type="submit" id="postive" value="postive">Answer Now</button>
      </div>
    </form>
  </dialog>
  <script>
    // <dialog> 예제. 러프하게 그냥 script 태그에 인라인으로 작성해볼게요.
    
    const dialog = document.querySelector('dialog#myDialog'); // dialog 태그를 dialog 상수에 담음.
    const message = document.querySelector('#message'); // message 상수에 대화상자가 닫혔을 때 쓰일 결과를 표시할 영역 p#message 태그를 가져옴.
    
    // 구조 분해 할당, 변수 선언 키워드인 const나 let을 쓰고, 배열 형식으로 이름을 작성하고, 배열 수에 맞게, 배열 순서에 원하는 값을 넣어 대입
    const [btnOpen,btnClose,btnAnswerNegative,btnAnswerPostive, typeName] = [
      document.querySelector('#btnOpen'), // btnOpen에 담김 (body button#btnOpen)
      document.querySelector('#btnClose'), // btnClose에 담김 (dialog button#btnClose)
      document.querySelector('#negative'), // btnAnswerNegative에 담김 (dialog button#negative)
      document.querySelector('#postive'), // btnAnswerPostive에 담김 (dialog button#postive)
      document.querySelector('#typeName') // typeName에 담김 (input#typeName)
    ];

    // 여는 함수 / 닫는 함수, 굳이 안 만들어도 되지만 조금 더 타이핑을 줄이기 위해...
    function close() {
      dialog.close();
    }
    function show() {
      dialog.showModal();
    }

    // 여는 이벤트 구현
    // HTMLDialogElement.show()로 열면 비모달 대화상자, HTMLDialogElement.showModal()로 열면 모달 대화상자가 열림. show()함수에 showModal() 메소드가 사용됨
    btnOpen.addEventListener('click',function(){
      show();
    })

    // X 버튼을 눌러 닫으면
    btnClose.addEventListener('click',function(){
      close(); // close 버튼이 눌리면 닫는 함수 동작.
    })
    

    // 편집창 값 바뀔 때 긍정 버튼의 value 속성을 바꿔서 반환시킨다.
    typeName.addEventListener('keydown',function (evt) { // typeName 입력 서식에서 키보드 이벤트를 수신, 함수의 evt 인수는 이벤트 객체를 의미함. 키값을 인식하거나, 이벤트 기본값을 제거하거나 할 때 참조해야 하기 때매 필요함.
      switch(evt.code){ // evt.code 값을 참조
        case "Enter": // evt.code의 반환값이 "Enter" 스트링이면,
          evt.preventDefault();//preventDefault로 기본 키 동작을 무시
          dialog.querySelector(".default").click(); // Enter가 눌리면 dialog의 기본 응답 동작을 무시하고, default 클래스가 부여된 버튼이 눌리도록함.
          break;
      }
    })

    
    dialog.addEventListener('close',function(){ // dialog가 닫혔을 때에 대한 Event
        switch(dialog.returnValue) {
          case "cancel":
            return false; // Cancel이면 아무 동작이 없음.
          case "negative": // 부정적인 답변을 하면, 아래 메시지를 뱉음.
            message.innerHTML = "Come again anytime if you're possible.";
            break;
          case "postive": // 긍정적인 답변을 하면 input 값을 반환하며 닫힘.
            dialog.returnValue = typeName.value;//postive 값의 버튼이 눌리면,
            if (dialog.returnValue) { // returnValue값(이름을 묻는 input 값)이 비어 있지 않다면
              message.innerHTML = `Hello, ${dialog.returnValue} :)`;
            } else { // returnValue값(이름을 묻는 input 값)이 비어있다면,
              message.innerHTML = "Are you really have no name? If you are not, you don't want to tell me your name?"
            }
          break;
        }
    })
  </script>
</body>
</html>

자, HTML, JS, CSS코드를 이해하시는분이라면, 위 코드만으로도 주석을 군대군대 달아놨기 때문에 이해하는 데에 무리가 없을 겁니다. 그래도 한번 조금 정리해 볼게요.

  • <dialog> 태그를 선언하면, 아무일도 일어나지 않습니다. <dialog>태그는 자바스크립트를 통한 조작에 의존합니다.
  • <dialog> 태그 안에는 기존의 <form> 태그를 넣을 수 있습니다. <form>태그의 서버 통신 방법을 정의할 때 사용하던 속성 method에 새로운 속성값인 “dialog”가 추가되었습니다. “dialog”속성값을 사용하는 서식 컨테이너는 버튼이 눌렸을 때 버튼에 있는 value값이 대화상자 객체 내에 있는 속성에 반환됩니다. HTMLDialogElement.returnValue를 통해 반환된 값을 참조할 수 있습니다.
  • <dialog>를 Javascript로 열기 위해서는 대화상자 객체의 show() 메소드를 사용하거나, showModal() 메소드를 사용해야 합니다. show() 메소드는 비모달식 대화상자가 열리며, showModal()은 모달 대화상자를 표시합니다. 대화상자 용도에 맞게 사용해야 합니다.
  • <dialog>를 Javascript로 닫기 위해서는 대화상자 객체에서 close() 메소드를 사용하면 됩니다.
  • <dialog> 태그는 기본적인 접근성 기능이 모두 구현되어 있습니다.
    • 대화상자를 닫거나 값을 전송하기 위한 모든 키보드 이벤트와 모바일 OS의 네이티브 이벤트가 구현되어 있습니다. 네이티브 이벤트가 구현되어있다는 것은 iOS에서 VoiceOver로 두 손가락 문지르기를 통해 대화상자를 닫거나 하는 행위가 가능함을 말합니다.
    • 모달로 열린 대화상자는 닫기 전까지는 대화상자 내부에서만 탐색이 가능합니다. 키보드 초점뿐만 아니라, 스크린리더의 가상커서또한 마찬가지입니다.
  • <dialog>가 닫혔을 때를 감지하려면 대화상자 객체에 addEventListener메소드로 “close”를 이벤트 타입 인자에 넣어 이벤트를 수신합니다.

위에서 사용되지 않은 것들에 대해서도 정리해 볼게요.

  • <dialog>가 열려있는지 닫혀있는지 확인하기 위해서는 dialog.open (get, set)을 사용합니다. setter로 true값을 설정하면 비모달 상태의 대화상자가 표시됩니다.
  • <dialog>가 취소되었는지 감지하기 위해서는 addEventListener 메소드에 이벤트 타입 인자를 “cancel”을 넣어 이벤트를 수신합니다.
  • <dialog>가 모달상태로 표시되었을 때 대화상자 밖에 표시된 반투명한 레이어의 스타일을 변경하려면 ::backdrop css 선택자를 사용합니다.
  • <dialog> 태그 작성 시 open 속성을 제공하면 비모달 상태의 레이어가 펼쳐진 상태로 페이지를 불러올 수 있습니다.

자, <dialog>태그에 대해 간략히 설명해 보았는데, 어떤가요? 커스텀 대화상자를 만들 이유가 완전히 사라졌습니다! 정말 멋진 일이지 않나요? 완전히 표준화된, 어떤 플렛폼에서건, 나와 내 기업에 맞는 디자인으로 아름답게, 접근성도 좋고 안정성있는 <dialog> 태그, 안쓸 이유가 없지 않나요? 이번 아티클은 여기까지입니다. 이렇게 메리트가 많은 <dialog> 태그를 많은 사이트에서 활용하길 바라며, 아티클을 마칩니다.ㅡ

댓글 0
댓글을 작성하려면 해주세요.