Всем привет! Я в веб-разработке не так давно. Сейчас я пишу свой сайт, который будет выступать в качестве моего портфолио и, возможно, даже целого проекта. При добавлении модального окна для авторизации на свой сайт, я подумал, а что будет, если публиковать подобные модульные вещи, чтобы любой человек мог их скопировать и не думать о них, а использовать в разработке. То есть создать готовый модуль для встраивания. Мне, как разработчику было бы удобно использовать готовое решение, тем более написанное мною, да и делиться опытом - дело приятное :)
Для тех, кому надо сразу код и вид окна.
NB
сам сайт скрыт для сохранения интеллектуальной собственности :)
А теперь пошаговое создание:
<div class="modalBackground">
<div class="modalActive">
<div class="modalClose">
<img src="modalCross.svg" />
</div>
<div class="modalWindow"></div>
</div>
</div>
Здесь представлена несложная разметка для модального окна.
Перед разъяснением элементов и их стилей, мы укажем скрытие ползунка прокрутки для body, которое нам будет мешать:
/* убираем нижний ползунок прокрутки */
body {
overflow-x: hidden;
}
Давайте поясню, что есть что:
modalBackground - это фон, расположенный за модальным окном. Через него мы будем видеть наш сайт { background: rgba(0, 0, 0, 0.8); }, в то время как основной фокус будет на модальном окне. Для корректного расположения, чтобы наш фон покрывал весь сайт - мы делаем его с положением { position: fixed } и размерами на весь экран { width: 100%; height : 100%; }. А для наложения поверх других окон или элементов сайта - { z-index: 1 }. Стоит отметить, что изначально мы не можем видеть наше окно { display: none }. Для отображения возможности нажатия на фон - делаем указатель через { cursor: pointer; }
/* фон нашего модального окна */
.modalBackground {
display: none;
background: rgba(0, 0, 0, 0.8);
position: fixed;
width: 100%;
height: 100%;
cursor: pointer;
/* указываем z-индекс для корректного наслаивания */
z-index: 1;
}
modalActive - непосредственно наше модальное окно. Его размеры { width: 350px; height : 495px; } я подбирал исходя из удобства верстки для маленьких экранов, так что размеры можно указать свои. Мы позиционируем наше модальное окно по центру { position: absolute; top: calc(50% - 250px); left: calc(50% - 175px); }. Делаем рамки закругления в 10 пикселей { border-radius: 10px }. Курсор для удобства делаем стандартным { cursor: default }. Свойства background-color и padding в данном случае ни на что не влияют, они используются для фона нашего окошечка внутри и для внутренних отступов нашего окошечка соответственно. Поэтому их можно ставить на свой вкус и цвет :)
/* позиционирование самого модального окна */
.modalActive {
position: absolute;
width: 350px;
height: 495px;
top: calc(50% - 250px);
left: calc(50% - 175px);
border-radius: 10px;
background-color: rgb(255, 255, 255);
cursor: default;
padding: 40px 20px;
}
modalClose - это наш крестик. Я использовал два варианта выхода из модального окна, кому как удобнее - через нажатие на фон, либо через нажатие на крестик (этот вариант предпочтительнее для мобильных устройств из-за маленького экрана, по сравнению с монитором ноутбука/компьютера/планшета). Для крестика соответственно используется тег modalCross с указанием в img пути к картинке-крестика. Свойство font-family используется для выбора семейства шрифта, здесь оно не играет роли (на ваш вкус и цвет).
/* кнопочка закрытия модального окна */
.modalClose {
font-family: var(--font-regular);
position: absolute;
right: 5px;
top: 5px;
width: 30px;
height: 30px;
cursor: pointer;
}
/* сама картинка кнопочки закрытия */
.modalClose img {
margin: 3px;
width: 24px;
height: 24px;
}
modalWindow - внутри него располагается содержимое нашего окна, для удобной верстки внутреннего содержимого устанавливаем относительное позиционирование относительно нашего модального окна { position: relative }. Какое оно будет - зависит только от вас :)
/* делаем позиционирование внутренних элементов относительно модального окна */
.modalWindow {
position: relative;
}
Теперь приступим к части оживления нашего окна с помощью жабаскрипта JS !
Прежде всего нам надо получить все элементы, с которыми нам предстоит работать:
// устанавливаем триггер для модального окна (название можно изменить)
const modalTrigger = document.getElementsByClassName("trigger")[0];
// получаем ширину отображенного содержимого и толщину ползунка прокрутки
const windowInnerWidth = document.documentElement.clientWidth;
const scrollbarWidth = parseInt(window.innerWidth) - parseInt(document.documentElement.clientWidth);
// привязываем необходимые элементы
const bodyElementHTML = document.getElementsByTagName("body")[0];
const modalBackground = document.getElementsByClassName("modalBackground")[0];
const modalClose = document.getElementsByClassName("modalClose")[0];
const modalActive = document.getElementsByClassName("modalActive")[0];
Стоит отметить, что при использовании метода getElementsByClassName мы будем получать массив всех элементов. Поэтому для корректного обращения к конкретному элементу - мы указываем индекс получаемого элемента. А так как наши массивы состоят из одного элемента - индекс во всех случаях равен [0].
Переменная modalTrigger - содержит наш триггер, при нажатии на который у нас будет появляться наше модальное окно.
Если наш сайт длинный и у нас появляется ползунок прокрутки - мы получаем его длину и записываем в scrollbarWidth. Давайте тут поподробнее. Мы изначально получаем ширину видимой части сайта через свойство clientWidth. Ширина ползунка прокрутки - разность между шириной окна внутри и шириной отображаемого содержимого (то, что под ползунком - не является отображаемым содержимым). Для корректности мы преобразовываем полученные значения через функции parseInt.
Далее мы привязываем необходимые элементы к переменным в соответствии с названием их классов, для тега body мы используем метод getElementsByTagName с указанием индекса получаемого элемента, то есть [0]. Смысл такой же, как и при getElementsByClassName. Я заметил простую вещь - если читаешь название методов, то понимаешь что они тебе дают :). В нашем случаем мы получаем множество элементов (окончание s в слове Elements), поэтому мы и работаем с массивом. А в методе getElementById мы получаем один элемент (о чем свидетельствует отсутствие окончания s в слове Element).
После инициализации рабочих переменных - мы корректируем положение body при появлении ползунка прокрутки, то есть наш ползунок будет накладываться поверх нашего сайта, а не сдвигать его, как это обычно бывает (можете проверить сами):
// функция для корректировки положения body при появлении ползунка прокрутки
function bodyMargin() {
bodyElementHTML.style.marginRight = "-" + scrollbarWidth + "px";
}
// при длинной странице - корректируем сразу
bodyMargin();
Механизм корректировки прост: в случае появления ползунка прокрутки - мы делаем отступ body через свойство margin-right на отрицательную величину ширины ползунка прокрутки. При первой загрузки страницы мы вызываем эту функцию, чтобы наше содержимое сразу же позиционировалось корректно, несмотря на наличие ползунка прокрутки. Вообще, позиционирование с учетом ползунка прокрутки - дело каждого, мне не хотелось бы, чтобы мой сайт скакал влево вправо :)
Создадим обработчик события нажатия на наш триггер:
// событие нажатия на триггер открытия модального окна
modalTrigger.addEventListener("click", function () {
// делаем модальное окно видимым
modalBackground.style.display = "block";
// если размер экрана больше 1366 пикселей (т.е. на мониторе может появиться ползунок)
if (windowInnerWidth >= 1366) {
bodyMargin();
}
// позиционируем наше окно по середине, где 175 - половина ширины модального окна
modalActive.style.left = "calc(50% - " + (175 - scrollbarWidth / 2) + "px)";
});
И в завершение создадим обработчик закрытия нашего окна при нажатии на крестик или на область за модальным окном:
// нажатие на крестик закрытия модального окна
modalClose.addEventListener("click", function () {
modalBackground.style.display = "none";
if (windowInnerWidth >= 1366) {
bodyMargin();
}
});
// закрытие модального окна на зону вне окна, т.е. на фон
modalBackground.addEventListener("click", function (event) {
if (event.target === modalBackground) {
modalBackground.style.display = "none";
if (windowInnerWidth >= 1366) {
bodyMargin();
}
}
});
Вот и всё модальное окно. Если у Вас есть какие-нибудь замечания, похвала, критика, советы, любой фидбек - буду рад прочитать и внести корректировки! Всех обнял-приподнял :)
Комментарии (28)
aio350
00.00.0000 00:00+17Простейшая модалка:
<dialog style="padding: 0"> <div id="modal-box" style="padding: 1rem"> <div>Modal content</div> <button id="close-modal-btn">Close</button> </div> </dialog> <button id="show-modal-btn">Show modal</button>
const modal = document.querySelector('dialog') const modalBox = document.getElementById('modal-box') const showModalBtn = document.getElementById('show-modal-btn') const closeModalBtn = document.getElementById('close-modal-btn') let isModalOpen = false showModalBtn.addEventListener('click', (e) => { modal.showModal() isModalOpen = true e.stopPropagation() }) closeModalBtn.addEventListener('click', () => { modal.close() isModalOpen = false }) document.addEventListener('click', (e) => { if (isModalOpen && !modalBox.contains(e.target)) { modal.close() } })
LinaRSH
00.00.0000 00:00Я ждал Ваш комментарий. Если бы была возможность — плюсанул бы Вам за использование нативных инструментов без костылей и велосипедов
hahavenn Автор
00.00.0000 00:00Тег dialog является экспериментальным. Взять тот же mdn, к примеру firefox. Версия, где поддерживается этот тег выпущена 2022-03-08. Для safari аналогичная ситуация - 2022-03-15. Поэтому на простых костылях сделал окно для возможности использования без проблем в старых браузерах. Спасибо за комментарий в любом случае!
alexnozer
00.00.0000 00:00Проблема с плохой поддержкой
dialog
решается при помощи полифила. В браузере проверяется поддержкаdialog
и этот скрипт подключается при помощи динамического импорта, если браузер не поддерживаетdialog
.Преимущества этого элемента в том, что он нативный, имеет встроенные методы и атрибуты для открытия/закрытия, семантику и доступен из коробки.
Спроектировать хорошее модальное окно не такая уж и тривиальная задача. Нужно стилиовать окно и бэкдроп, при этом предусмотреть возможность кастомизации. Нужно написать логику и API для работы, при этом предусмотреть различные сценарии использования. Нужно позаботиться о семантике и сделать его действительно модальным - роль, все нужные
aria
-состояния иfocus trap
. Нужно продумать работу с клавиатуры, перемещение по интерактивным элементам внутри, закрытие через крестик иEsc
. Нужно правильно реализовать бэкдроп, чтобы контент под ним не был интерактивным и страница не прокручивалась. Также нужно учесть, что в окне может быть разный контент и не допустить переполнения. И это все ещё нужно реализовать максимально кросбраузерно.hahavenn Автор
00.00.0000 00:00Спасибо за развернутый и подробный комментарий! В данном примере я не старался использовать полифилы лишь по одной причине - данный тип модального окна, на мой взгляд, достаточно прост и не занимает много строчек кода, используя также самые обычные средства для реализации. Вся логика и API предоставляются для Читателя. Об этом я постарался подумать и сделал возможность позиционирования и верстки элементов внутри окна при открытии. Все остальные пожелания или изменения в работе окна, а также взаимодействие с ним - индивидуальны и зависят от задачи)
scoffs
00.00.0000 00:00+2z-index всё же следует поднять, потому что на сайте могут быть обычные элементы, у которых z-index больше одного.
hahavenn Автор
00.00.0000 00:00Да, Вы правы, величину надо регулировать в зависимости от необходимого наслоения. Я указал его равным 1 из соображений, что элемент со свойством z-index один на странице. Спасибо!
ZetaTetra
00.00.0000 00:00+3Такой крестик можно нарисовать используя CSS transform:
.modalClose { float:right; cursor:pointer; position:relative; width:20px; height:20px; display:flex; flex-direction:column; justify-content:center } .modalClose::before, .modalClose::after { position:absolute; content:''; width:100%; height:2px; background-color:#000; } .modalClose::before { transform:rotate(45deg); } .modalClose::after { transform:rotate(-45deg) }
В таком случае будет проще добавить, скажем, hover, а не заморачиваться со сменой цветов в SVG через filter:
.modalClose:hover::before, .modalClose:hover::after { background-color:Red; }
hahavenn Автор
00.00.0000 00:00Спасибо за фидбек! Честно, не хотелось особо сильно заморачиваться с CSS. Поэтому и сделал расчет из удобства, чтобы вставить любую картинку, выступающую в роли "крестика"
BigDflz
00.00.0000 00:00+6хабр стал похож на отчетную страницу первого класса, огромное достижение - модальное окно....
ну ж если и выкладывать - то уж хотя бы c с использованием template, Shadow DOM, grid, ну и возможностью перемещения этого окна по экрану.и за что плюсанули в карму? за приглашение от НЛО?
hahavenn Автор
00.00.0000 00:00Спасибо за комментарий! Первая мысль, как сделать модальное окно - с использованием самых простых вещей без особого изощрения. Если есть примеры создания модального окна с этими инструментами - я буду рад их увидеть! Честно говоря не представляю как использовать теневое дерево, разве что скрыть модальное окно, чтобы его нельзя было достаточно легко отобразить через средства разработчиков. Но опять же, не совсем знаю, как эта технология устроена, ибо не использую, а читал для ознакомления.
MrRewolwer
00.00.0000 00:00Зачем вы так говорите? Это же модалка на ЧИСТОМ CSS и JS. Раньше модалку можно было только на React + Tailwind, а теперь вот, прогресс!
hahavenn Автор
00.00.0000 00:00Спасибо за комментарий! Я рад, что у Вас получилось освоить построение веб-страниц сразу же начиная с использованием фреймворков. Простое модальное окно на ванильном JS и CSS ничем не хуже их аналогов с использованием соответствующих библиотек, тем более в обучении (как я и указал, что не так давно занялся веб-разработкой)
dmitry-rakovich
00.00.0000 00:00Для этого в html добавили тег dialog, со встроенными методами открытия и закрытия через js и оверлеем)
hahavenn Автор
00.00.0000 00:00Да, он является экспериментальным. Хоть и поддержка от chrome была ещё в 2014 году, но тот же самый safari и firefox начали поддерживать его в марте 2022 года. Поэтому я и решил сделать простейшее модальное окно с использованием "палок". В любом случае - спасибо за фидбек!
web3_Venture
00.00.0000 00:00Я ожидал увидеть нативный dialog , после прочтения статьи еще раз заголовок прочитал...
hahavenn Автор
00.00.0000 00:00Нативный dialog поддерживается не так давно в полном объеме, о чем пишет mdn. Поэтому и модальное окно сделано "нативнее" нативного :). Спасибо за фидбек!
Serator
00.00.0000 00:00А модальность окна в чем заключается? Статья про
div
в виде окна, не более. Нативныйdialog
- не просто тег и он, как минимум, уже поддерживается. Почитайте еще про атрибутinert
, техникуfocus trap
,accessibility
... И обновите свой пример, он сломан, так какmodalTrigger
пропущен.hahavenn Автор
00.00.0000 00:00Спасибо за комментарий! Модальность заключается в том, что при нажатии на modalTrigger у нас открывается окно поверх других элементов на странице. Сам же modalTrigger я отдельно вынес и указал, что его задаем сами (т.е. любой элемент на Вашей странице). Спасибо также за материал, прочитаю и ознакомлюсь!
Serator
00.00.0000 00:00Модальность должна ограничивать пользователя во взаимодействии с родительским окном. В вашем случае доступ с клавиатуры никак не блокируется. Только указатель (к примеру, курсор мыши) и то при условии, что всякие там
z-index
у родителя и его потомков не будут перекрывать ваше окно. Проблема всей статьи в том, что слой поверх слоя можно было и 10 лет назад сделать относительно просто на "чистом CSS и JS", а вот именно модальное окно уже сильно сложней, если вообще осуществимо даже сейчас (т.е. воссоздать поведение нативногоdialog
в полной мере).
hitriymuh
00.00.0000 00:00+1.modalActive { position: absolute; width: 350px; height: 495px; top: 50%; left: 50%; border-radius: 10px; background-color: rgb(255, 255, 255); cursor: default; padding: 40px 20px; transform: translate(-50%, -50%) }
Если в дальнейшем потребуется изменение размеров модального окна у вас возникнет потребность с пересчетом
top
иleft
. Используйте для центрированиеtransform
. Почитать подробнее про него можно здесь.hahavenn Автор
00.00.0000 00:00Спасибо! Да, такой способ центрирования намного удобнее и не зависит от параметров окна. Применил у себя :)
upd: при наличии ползунка прокрутки модальное окно не учитывает его величину и надо задавать дополнительный отступ, чтобы окно позиционировалось по центру. Этот способ удобен, когда мы не учитываем длину страницы по оси Y и нам неважно, будет ли сайт смещаться влево или нет. В моём случае я дополнительно смещаю сайт, чтобы он всегда был по центру, поэтому надо продумать момент с позиционированием окна тоже
aleksandy
Есть подозрение, что
else
-ветка тут лишняя.Или...
... условие надо на
==
переделать.hahavenn Автор
Прочитал Ваш комментарий, да, функция немного была некорректно составлена с лишними телодвижениями, теперь достаточно просто сделать отступ на величину ширины ползунка прокрутки. Спасибо!