Клавиша / esc

Попап

Верстаем попап на <dialog> с использованием всех возможностей тега.

Время чтения: 7 мин

Задача

Скопировано

Создание попапа — распространённая задача для разработчика. Попапы или модальные окна привлекают внимание. Это может быть полезно как для самого пользователя, так и для заказчика. Например, попап — это удобный способ предупреждения о невозвратности действия при попытке перезагрузки страницы, а также хороший инструмент для сбора контактов пользователей.

В статье покажем простой и понятный способ создания попапа с использование тега <dialog>.

Готовое решение

Скопировано

Для начала создадим HTML-разметку со всеми необходимыми элементами:

        
          
          <body class="parent">  <button    class="openDialogBtn button-violet"    type="button"    aria-haspopup="dialog"    aria-controls="myDialog"  >    Открыть попап  </button>  <dialog class="child" id="myDialog">    <div class="dialog__wrapper">      <h2>Дока — самая добрая документация &#128579</h2>      <button        class="closeDialogBtn button-black"        type="button"      >        Согласен &#128156      </button>    </div>  </dialog></body>
          <body class="parent">
  <button
    class="openDialogBtn button-violet"
    type="button"
    aria-haspopup="dialog"
    aria-controls="myDialog"
  >
    Открыть попап
  </button>
  <dialog class="child" id="myDialog">
    <div class="dialog__wrapper">
      <h2>Дока — самая добрая документация &#128579</h2>
      <button
        class="closeDialogBtn button-black"
        type="button"
      >
        Согласен &#128156
      </button>
    </div>
  </dialog>
</body>

        
        
          
        
      

Для внешнего оформления, а также правильной работы попапа, нам понадобятся следующие CSS-правила:

        
          
          body {  min-height: 100vh;  padding: 50px;  display: grid;  justify-items: center;  align-items: center;  background-color: #18191C;  color: #FFFFFF;  font-family: "Roboto", sans-serif;}dialog {  position: fixed;  height: 250px;  width: 350px;  top: 50%;  left: 50%;  transform: translate(-50%, -50%);  border: none;  padding: 0;  background-color: #FFFFFF;  color: #000000;  text-align: center;}.openDialogBtn {  position: fixed;  bottom: 50px;  right: 50px;  min-width: 210px;  border: 2px solid transparent;  border-radius: 6px;  font-size: 18px;  font-weight: 300;  font-family: inherit;  transition: background-color 0.2s linear;}.closeDialogBtn {  margin: 15% auto 0;  border: 2px solid transparent;  min-width: 210px;  border-radius: 6px;  padding: 9px 15px;  color: #000000;  font-size: 18px;  font-weight: 300;  font-family: inherit;  transition: background-color 0.2s linear;  display: flex;  justify-content: center;  align-items: center;  color: white;}.dialog__wrapper {  padding: 1em;}dialog::backdrop {  background-color: rgb(0 0 0 / 0.8);}.scroll-lock {  overflow: hidden;}
          body {
  min-height: 100vh;
  padding: 50px;
  display: grid;
  justify-items: center;
  align-items: center;
  background-color: #18191C;
  color: #FFFFFF;
  font-family: "Roboto", sans-serif;
}

dialog {
  position: fixed;
  height: 250px;
  width: 350px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: none;
  padding: 0;
  background-color: #FFFFFF;
  color: #000000;
  text-align: center;
}

.openDialogBtn {
  position: fixed;
  bottom: 50px;
  right: 50px;
  min-width: 210px;
  border: 2px solid transparent;
  border-radius: 6px;
  font-size: 18px;
  font-weight: 300;
  font-family: inherit;
  transition: background-color 0.2s linear;
}

.closeDialogBtn {
  margin: 15% auto 0;
  border: 2px solid transparent;
  min-width: 210px;
  border-radius: 6px;
  padding: 9px 15px;
  color: #000000;
  font-size: 18px;
  font-weight: 300;
  font-family: inherit;
  transition: background-color 0.2s linear;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
}

.dialog__wrapper {
  padding: 1em;
}

dialog::backdrop {
  background-color: rgb(0 0 0 / 0.8);
}

.scroll-lock {
  overflow: hidden;
}

        
        
          
        
      

Реализуем открытие и закрытие попапа с помощью JavaScript-методов:

        
          
          const dialog = document.getElementById('myDialog')const dialogOpener = document.querySelector('.openDialogBtn')const dialogCloser = dialog.querySelector('.closeDialogBtn')function closeOnBackDropClick({ currentTarget, target }) {  const dialog = currentTarget  const isClickedOnBackDrop = target === dialog  if (isClickedOnBackDrop) {    close()  }}function openModalAndLockScroll() {  dialog.showModal()  document.body.classList.add('scroll-lock')}function returnScroll() {  document.body.classList.remove('scroll-lock')}function close() {  dialog.close()  returnScroll()}dialog.addEventListener('click', closeOnBackDropClick)dialog.addEventListener('cancel', (event) => {  returnScroll()});dialogOpener.addEventListener('click', openModalAndLockScroll)dialogCloser.addEventListener('click', (event) => {  event.stopPropagation()  close()})
          const dialog = document.getElementById('myDialog')
const dialogOpener = document.querySelector('.openDialogBtn')
const dialogCloser = dialog.querySelector('.closeDialogBtn')

function closeOnBackDropClick({ currentTarget, target }) {
  const dialog = currentTarget
  const isClickedOnBackDrop = target === dialog
  if (isClickedOnBackDrop) {
    close()
  }
}

function openModalAndLockScroll() {
  dialog.showModal()
  document.body.classList.add('scroll-lock')
}

function returnScroll() {
  document.body.classList.remove('scroll-lock')
}

function close() {
  dialog.close()
  returnScroll()
}

dialog.addEventListener('click', closeOnBackDropClick)
dialog.addEventListener('cancel', (event) => {
  returnScroll()
});
dialogOpener.addEventListener('click', openModalAndLockScroll)
dialogCloser.addEventListener('click', (event) => {
  event.stopPropagation()
  close()
})

        
        
          
        
      
Открыть демо в новой вкладке

Разбор решения

Скопировано

Разметка

Скопировано

Сделаем <dialog> дочерним элементом относительно <body>. Это позволит нам в дальнейшем расположить попап по центру экрана. Текст и кнопку внутри модального окна обернём в тег с классом dialogWrapper. С помощью этой обёртки мы реализуем закрытия попапа по клику на тёмную область (оверлей). Атрибуты aria-haspopup и aria-controls сообщают вспомогательным технологиям о том, как связаны элементы. Так они повышают доступность приложения для пользователей.

        
          
          <body class="parent">  <button    class="openDialogBtn button-violet"    type="button"    aria-haspopup="dialog"    aria-controls="myDialog"  >    Открыть попап  </button>  <dialog class="child" id="myDialog">    <div class="dialog__wrapper">      <h2>Дока — самая добрая документация &#128579</h2>      <button        class="closeDialogBtn button-black"        type="button"      >        Согласен &#128156      </button>    </div>  </dialog></body>
          <body class="parent">
  <button
    class="openDialogBtn button-violet"
    type="button"
    aria-haspopup="dialog"
    aria-controls="myDialog"
  >
    Открыть попап
  </button>
  <dialog class="child" id="myDialog">
    <div class="dialog__wrapper">
      <h2>Дока — самая добрая документация &#128579</h2>
      <button
        class="closeDialogBtn button-black"
        type="button"
      >
        Согласен &#128156
      </button>
    </div>
  </dialog>
</body>

        
        
          
        
      

Стили

Скопировано

Для центрирования попапа на странице присвоим ему position: fixed, а также зададим 50% для его расположения по осям для top и left:

        
          
          dialog {  position: fixed;  height: 250px;  width: 350px;  top: 50%;  left: 50%;  transform: translate(-50%, -50%);  border: none;  padding: 0;  background-color: #FFFFFF;  color: #000000;  text-align: center;}
          dialog {
  position: fixed;
  height: 250px;
  width: 350px;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border: none;
  padding: 0;
  background-color: #FFFFFF;
  color: #000000;
  text-align: center;
}

        
        
          
        
      

Встроенной функцией тега <dialog> является его подложка. Она появляется в момент открытия попапа и имеет название ::backdrop. С помощью этого псевдоэлемента мы можем стилизовать задник модального окна, а также реализовать его закрытие по клику на оверлей. Первое реализуем с помощью правил CSS — добавим затемнение на экран за открытым попапом:

        
          
          dialog::backdrop {  background-color: rgb(0 0 0 / 0.8);}
          dialog::backdrop {
  background-color: rgb(0 0 0 / 0.8);
}

        
        
          
        
      

Если на странице с попапом есть скролл, его можно заблокировать с помощью свойства scrollbar-gutter для нашего <body>. Это позволит нам полностью сконцентрировать внимание пользователя на модальном окне и запретить прокрутку контента на странице за ним.

        
          
          body {  min-height: 100vh;  padding: 50px;  background-color: #18191c;  color: #ffffff;  font-family: "Roboto", sans-serif;  font-size: 18px;  scrollbar-gutter: stable;}.scroll-lock {  overflow: hidden;}
          body {
  min-height: 100vh;
  padding: 50px;
  background-color: #18191c;
  color: #ffffff;
  font-family: "Roboto", sans-serif;
  font-size: 18px;
  scrollbar-gutter: stable;
}

.scroll-lock {
  overflow: hidden;
}

        
        
          
        
      

JavaScript

Скопировано

Для начала найдём все элементы, которые понадобятся нам для работы с попапом — модальное окно и кнопки для его открытия и закрытия:

        
          
          const dialog = document.getElementById('myDialog')const dialogOpener = document.querySelector('.openDialogBtn')const dialogCloser = dialog.querySelector('.closeDialogBtn')
          const dialog = document.getElementById('myDialog')
const dialogOpener = document.querySelector('.openDialogBtn')
const dialogCloser = dialog.querySelector('.closeDialogBtn')

        
        
          
        
      

Напишем функции для открытия и закрытия попапа. Также поместим в них код, необходимый для блокировки скролла страницы. Не забудем вернуть скролл обратно при закрытии попапа:

        
          
          function openModalAndLockScroll() {  dialog.showModal()  document.body.classList.add('scroll-lock')}function returnScroll() {  document.body.classList.remove('scroll-lock')}function close() {  dialog.close()  returnScroll()}
          function openModalAndLockScroll() {
  dialog.showModal()
  document.body.classList.add('scroll-lock')
}

function returnScroll() {
  document.body.classList.remove('scroll-lock')
}

function close() {
  dialog.close()
  returnScroll()
}

        
        
          
        
      

Навесим соответствующие обработчики событий на наши кнопки:

        
          
          dialogOpener.addEventListener('click', openModalAndLockScroll)dialogCloser.addEventListener('click', (event) => {  event.stopPropagation()  close()})
          dialogOpener.addEventListener('click', openModalAndLockScroll)
dialogCloser.addEventListener('click', (event) => {
  event.stopPropagation()
  close()
})

        
        
          
        
      

В коде выше мы поместили stopPropagation() внутрь обработчика события на кнопку закрытия попапа. Это необходимо для того, чтобы реализовать закрытие модального окна по клику на оверлей. Снова не будем забывать о возвращении скролла странице:

        
          
          function closeOnBackDropClick({ currentTarget, target }) {  const dialog = currentTarget  const isClickedOnBackDrop = target === dialog  if (isClickedOnBackDrop) {    close()  }}dialog.addEventListener('click', closeOnBackDropClick)dialog.addEventListener('cancel', (event) => {  returnScroll()});
          function closeOnBackDropClick({ currentTarget, target }) {
  const dialog = currentTarget
  const isClickedOnBackDrop = target === dialog
  if (isClickedOnBackDrop) {
    close()
  }
}

dialog.addEventListener('click', closeOnBackDropClick)
dialog.addEventListener('cancel', (event) => {
  returnScroll()
});

        
        
          
        
      

Другой функцией нашего попапа окажется его закрытие по нажатию на клавишу Esc. Это является встроенной функцией элемента <dialog> и не требует дополнительного кода. Несмотря на то, что закрытие произойдёт автоматически, нам нужно знать об этом событии, чтобы вернуть скролл. Для этого добавим обработку события 'cancel'.