Задача
СкопированоСоздание попапа — распространённая задача для разработчика. Попапы или модальные окна привлекают внимание. Это может быть полезно как для самого пользователя, так и для заказчика. Например, попап — это удобный способ предупреждения о невозвратности действия при попытке перезагрузки страницы, а также хороший инструмент для сбора контактов пользователей.
В статье покажем простой и понятный способ создания попапа с использование тега <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>Дока — самая добрая документация 🙃</h2> <button class="closeDialogBtn button-black" type="button" > Согласен 💜 </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>Дока — самая добрая документация 🙃</h2>
<button
class="closeDialogBtn button-black"
type="button"
>
Согласен 💜
</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>. Это позволит нам в дальнейшем расположить попап по центру экрана. Текст и кнопку внутри модального окна обернём в тег с классом dialog. С помощью этой обёртки мы реализуем закрытия попапа по клику на тёмную область (оверлей). Атрибуты aria и aria сообщают вспомогательным технологиям о том, как связаны элементы. Так они повышают доступность приложения для пользователей.
<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>Дока — самая добрая документация 🙃</h2> <button class="closeDialogBtn button-black" type="button" > Согласен 💜 </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>Дока — самая добрая документация 🙃</h2>
<button
class="closeDialogBtn button-black"
type="button"
>
Согласен 💜
</button>
</div>
</dialog>
</body>
Стили
СкопированоДля центрирования попапа на странице присвоим ему position, а также зададим 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> является его подложка. Она появляется в момент открытия попапа и имеет название :. С помощью этого псевдоэлемента мы можем стилизовать задник модального окна, а также реализовать его закрытие по клику на оверлей. Первое реализуем с помощью правил CSS — добавим затемнение на экран за открытым попапом:
dialog::backdrop { background-color: rgb(0 0 0 / 0.8);}
dialog::backdrop {
background-color: rgb(0 0 0 / 0.8);
}
Если на странице с попапом есть скролл, его можно заблокировать с помощью свойства scrollbar для нашего <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()
})
В коде выше мы поместили stop внутрь обработчика события на кнопку закрытия попапа. Это необходимо для того, чтобы реализовать закрытие модального окна по клику на оверлей. Снова не будем забывать о возвращении скролла странице:
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'.