Модальное окно на JavaScript

Модальное окно: принцип работы


Итак, разберём для начала, в общем, как реализован механизм работы модального окна.
Во-первых, само модальное окно - это, естественно, блок html-кода. И этот блок изначально на странице скрыт.
Чтобы показать модальное окно, нужно какое-то действие, например, клик мышкой по определённой ссылке на странице сайта, либо по какой-то кнопке и т.д.

Скрыть открывшееся модальное окно можно либо кликом по определённой ссылке, кнопке, либо кликнув в любое место вне контента модального окна.
Как вариант, существует возможность реализации скрытия модального окна при нажатии какой-то клавиши на клавиатуре (например, клавиши Escape)

При открытом модальном окне весь остальной контент страницы должен быть неактивным (страница не скроллится, неактивны элементы страницы).
Позиционирование на странице и визуальное оформление модального окна и его содержимого обеспечивается CSS-стилями.

Вот, по сути, и весь принцип работы модальных окон.

Создаём html-структуру и пишем стилизацию


Давайте создадим папку с проектом и назовём её modal. Внутри этой папки сделаем папки js и css.
В папке js создадим файл script.js, в папке css создадим файл style.css

В корневой директории папки с проектом создаём файл index.html и в нём подключим ранее созданные нами файлы style.css и script.js

Код

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Модальное окно на JavaScript</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
   
  <script src="js/script.js"></script>
</body>
</html>

На нашей странице давайте реализуем модальное окно с какой-нибудь формой обратной связи. При этом создадим два элемента-триггера, при клике по любому из которых будет вызываться наше модальное окно. (так часто бывает, например, на лендинг-страничках, где могут размещаться несколько ссылок в разных местах страницы, при этом все они вызывают одно и тоже действие, например, модальное окно с формой обратной связи)

Пусть это будут две кнопки с классом .btn и атрибутом data-modal в каком то контейнере c классом .btnBlock:

Код

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Модальное окно на JavaScript</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
   
  <div class="btnBlock">
  <button data-modal class="btn">Кнопка 1</button>
  <button data-modal class="btn">Кнопка 2</button>
  </div>

  <script src="js/script.js"></script>
</body>
</html>

И давайте пропишем базовые стили для оформления контейнера и самих кнопок, не особо задумывайтесь над ними, это всего лишь визуальное оформление, нам гораздо важнее JavaScript:

Код

.btnBlock {
  width: 460px;
  height: 65px;
  display: flex;
  justify-content: space-between;
  margin: 30px;
}

.btn {
  width:220px;
  height:65px;
  display:flex;
  justify-content:center;
  align-items:center;
  background-color:#fff;
  font-size:18px;
  font-weight:700;
  border:1px solid rgba(0,0,0,.2);
  box-shadow:0 4px 15px rgba(0,0,0,.2);
  cursor:pointer;
  transition:.3s all;
  outline:0
}

.btn:hover {
  box-shadow:0 4px 30px rgba(0,0,0,.3)
}

Теперь нам нужно разместить в файле index.html код самого модального окна:

Код

  <div class="modal">
  <div class="modal__dialog">
  <div class="modal__content">
  <form action="#">
  <div data-close class="modal__close">×</div>
  <div class="modal__title">Мы свяжемся с вами как можно быстрее!</div>
  <input required placeholder="Ваше имя" name="name" type="text" class="modal__input">
  <input required placeholder="Ваш номер телефона" name="phone" type="phone" class="modal__input">
  <button class="btn btn_dark btn_min">Перезвонить мне</button>
  </form>
  </div>
  </div>
  </div>

Здесь у нас блок-обёртка с классом .modal, внутри которого помещён заголовок, форма обратной связи и какая-то кнопка.
Также есть блок с классом .modal__close и атрибутом data-close, который выводит крестик у модального окна, при клике по этому крестику оно будет закрываться.

В файле стилей CSS пропишем стили оформления для модального окна и его позиционирования на странице:

Код

.btn_dark{
  background-color:#303030;
  color:#fff;
  border:none
}
.btn_min{
  height:50px
}
.modal{
  position:fixed;
  top:0;
  left:0;
  z-index:1050;
  display:none;
  width:100%;
  height:100%;
  overflow:hidden;
  background-color:rgba(0,0,0,.5)
}
.modal__dialog{
  max-width:500px;
  margin:40px auto
}
.modal__content{
  position:relative;
  width:100%;
  padding:40px;
  background-color:#fff;
  border:1px solid rgba(0,0,0,.2);
  border-radius:4px;
  max-height:80vh;
  overflow-y:auto
}
.modal__close{
  position:absolute;
  top:8px;
  right:14px;
  font-size:30px;
  color:#000;
  opacity:.5;
  font-weight:700;
  border:none;
  background-color:transparent;
  cursor:pointer
}
.modal__title{
  text-align:center;
  font-size:22px;
  text-transform:uppercase
}
.modal__input{
  display:block;
  margin:20px auto 20px auto;
  width:280px;
  height:50px;
  background:#fff;
  box-shadow:0 4px 15px rgba(0,0,0,.2);
  border:none;
  font-size:18px;
  text-align:center;
  padding:0 20px;
  outline:0
}
.modal .btn{
  display:block;
  width:280px;
  margin:0 auto
}

Обратите внимание, что для класса .modal прописано свойство display со значением none, за счёт чего изначально на странице сайта модальное окно скрыто.

Показывать же (и скрывать) модальное окно мы будем с помощью JavaScript. И для этого, кстати, нам понадобятся два класса, которые нужно также добавить в таблицу стилей:

Код

.show{
  display:block
}
.hide{
  display:none
}

Пишем скрипт для работы модального окна


Первым делом, чтобы иметь возможность работы с элементами страницы, нужно их выбрать.
Выбираем кнопки-триггеры, блок модального окна и блок-триггер с крестиком, который закрывает модальное окно, сохраняем результаты в переменных:

Код

  const modalTrigger = document.querySelectorAll('[data-modal]'),
  modal = document.querySelector('.modal'),
  modalCloseBtn = document.querySelector('[data-close]');

Будем отталкиваться от того, что нам необходимы две функции: первая будет отвечать за открытие модальных окон, вторая - за закрытие.
В переменной modalTrigger у нас сохранена выборка всех элементов на странице с атрибутом data-modal.

Для начала давайте добавим обработчик события для этой переменной, который будет взаимодействовать только с первой кнопкой, чтобы понять механизм работы. Заменим на время querySelectorAll на querySelector.
Итак, вешаем обработчик события click:

Код

modalTrigger.addEventListener('click', () => {

});

Блок самого модального окна мы сохранили в переменную modal, поэтому с полным правом можем работать с этой переменной через classList добавляем класс .show для открытия модального окна и одновременно убираем класс .hide:

Код

modalTrigger.addEventListener('click', () => {
  modal.classList.add('show');
  modal.classList.remove('hide');
});

Аналогично прописываем код для переменной modalCloseBtn, в которую мы сохранили элемент, отвечающий за закрытие модального окна, только делаем наоборот: добавляем класс .hide и убираем класс .show:

Код

modalCloseBtn.addEventListener('click', () => {
  modal.classList.add('hide');
  modal.classList.remove('show');
});

Теперь при клике на блок с атрибутом data-close, содержащий кнопку крестик, модальное окно будет скрыто.

Когда у нас появляется модальное окно, нам нужно, чтобы не работал скролл страницы.
Это можно реализовать, добавив данную строку кода в обработчик события для modalTrigger:

Код

document.body.style.overflow = 'hidden';

для modalCloseBtn добавляем аналогичную строку, только в кавычках оставим пустую строку, тем самым скролл страницы после закрытия модального окна восстановится:

Код

document.body.style.overflow = ' ';

Код скрипта на данный момент:

Код

const modalTrigger = document.querySelector('[data-modal]'),
  modal = document.querySelector('.modal'),
  modalCloseBtn = document.querySelector('[data-close]');

modalTrigger.addEventListener('click', () => {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
});

modalCloseBtn.addEventListener('click', () => {
  modal.classList.add('hide');
  modal.classList.remove('show');
  document.body.style.overflow = '';
});

Вариант с использованием метода classList.toggle


Суть метода classList.toggle : если у элемента уже есть присвоенный класс, то classList.toggle его удаляет, если же класса нет - он его этому элементу добавляет.

Общий синтаксис метода такой:

Код

элемент.classList.toggle('класс');

С учётом вышесказанного наш предыдущий код можно переписать следующим образом:

Код

const modalTrigger = document.querySelector('[data-modal]'),
  modal = document.querySelector('.modal'),
  modalCloseBtn = document.querySelector('[data-close]');

modalTrigger.addEventListener('click', () => {
  modal.classList.toggle('show');
  document.body.style.overflow = 'hidden';
});

modalCloseBtn.addEventListener('click', () => {
  modal.classList.toggle('show');
  document.body.style.overflow = '';
});

Так как изначально у блока с классом .modal нет присвоенного дополнительно класса .show, то метод classList.toggle его добавляет (модальное окно появляется).
При клике на закрывающий окно крестик метод classList.toggle ''видит", что класс .show у элемента есть и поэтому он его удаляет (модальное окно исчезает).

Вернёмся к предыдущему нашему методу, без toggle, и продолжим работать уже с ним. Дело в том, что изначально на странице сайта мы разместили две кнопки:

Код

  <div class="btnBlock">
  <button data-modal class="btn">Кнопка 1</button>
  <button data-modal class="btn">Кнопка 2</button>
  </div>

Но тот код, который мы написали, делает работоспособной только первую из них, при клике по второй кнопке никакого модального окна вы не увидите (так как метод querySelector возвращает вам лишь первый элемент документа, соответствующий указанному селектору).

Заменим его обратно на querySelectorAll, чтобы получить выборку из всех кнопок с атрибутом data-modal.

И теперь, чтобы задействовать все элементы этой выборки, нам нужно их всех перебрать.
Используем для этого метод forEach():

Код

modalTrigger.forEach(btn => {
  btn.addEventListener('click', () => {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
  });
});

Реализуем закрытие модального окна при клике на подложке или при нажатии клавиши на клавиатуре


Чтобы у пользователя была возможность закрывать модальное окно не только кликая по иконке с крестиком, но и в любом другом месте страницы, нам нужно это реализовать опираясь на "подложку" модального окна, то есть на всю ту затемнённую область на странице, которая возникает при открытом модальном окне.

Подложкой в нашем случае является блок с классом .modal, он занимает всю ширину и высоту страницы, убедиться в этом можно посмотрев css-стили для класса .modal:

Код

.modal{
  position:fixed;
  top:0;
  left:0;
  z-index:1050;
  display:none;
  width:100%;
  height:100%;
  overflow:hidden;
  background-color:rgba(0,0,0,.5)
}

Вешаем обработчик события click непосредственно на блок с классом .modal и в анонимную функцию передаём объект event:

Код

modal.addEventListener('click', (e) => {

});

Дальше внутри функции мы будем проверять условие, что если пользователь кликнул в место, которое совпадает с блоком .modal(подложкой), то модальное окно будет закрываться. Для закрытия мы будем использовать содержимое функции из обработчика события написанного нами ранее для блока modalCloseBtn:

Код

modal.addEventListener('click', (e) => {
  if(e.target === modal) {
  modal.classList.add('hide');
  modal.classList.remove('show');
  document.body.style.overflow = '';
  }
});

Если какой-то код начинает повторяться - это веский повод вынести его в отдельную функцию и использовать уже её.
Поэтому создаём функцию с именем closeModal и помещаем в неё тот код, который у нас дважды повторялся в скрипте:

Код

function closeModal() {
  modal.classList.add('hide');
  modal.classList.remove('show');
  document.body.style.overflow = '';
}

И теперь мы можем заменить повторяющийся код в функциях обработчиков событий для modalCloseBtn и modal:

Код

modalCloseBtn.addEventListener('click', closeModal);

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

Причём, в первом случае мы функцию closeModal просто передаём, а во втором случае вызываем, при условии истинности условия.

Чтобы реализовать закрытие модального окна при нажатии кнопки на клавиатуре (например, кнопки Esc), мы можем использовать событие keydown.
Обработчик для события keydown(нажата клавиша клавиатуры) вешаем непосредстенно на document, так как нет какого-то конкретного элемента:

Код

document.addEventListener('keydown', (e) => {

});

Для нашей задачи у JavaScript для объекта события есть такие свойства как event.key и event.code, первое позволяет получать символ, а второе "физический код клавиши".
Используем свойство e.code для проверки условия, что если нажата клавиша, физический код которой полностью совпадает с Escape и что при этом модальное окно открыто, то вызываем функцию закрытия модального окна:

Код

document.addEventListener('keydown', (e) => {
  if(e.code === "Escape" && modal.classList.contains('show')) {
  closeModal();
  }
});

Информацию о различных нажатых клавишах клавиатуры можно почерпнуть здесь. Просто нажимаете кнопку на клавиатуре и получаете нужную информацию:



Посмотреть модальное окно в работе

Ссылка на GitHub

Автоматически появляющееся модальное окно на странице через определённое время


Очень часто на страницах сайта можно увидеть автоматически появляющееся модальное окно с какой-то информацией.

Чтобы реализовать такой функционал, необходимо, во-первых, задать временной интервал, после которого будет появляться модальное окно, вызвать функцию, открывающую это модальное окно, предотвратить при этом автоматическое открывание модального окна, если пользователь уже открыл его самостоятельно (например, на странице сайта есть кнопка, по клику на которую открывается то же самое модальное окно, что и всплывающее автоматически)

Итак, работаем с кодом, который уже написали выше. И первым делом давайте создадим функцию, открывающую модальное окно. Назовём её openModal и в тело этой функции вставим уже имеющийся у нас код, берём его отсюда:

Код

modalTrigger.forEach(btn => {
  btn.addEventListener('click', () => {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
  });
});

и вставляем в нашу функцию:

Код

function openModal() {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
}

заодно упростим код, из которого позаимствовали код для функции:

Код

modalTrigger.forEach(btn => {
  btn.addEventListener('click', openModal);
});

Чтобы создать временной интервал, создадим переменную modalTimerId, в которую поместим setTimeout() с вызовом функции открытия модального окна через 3 секунды:

Код

const modalTimerId = setTimeout(openModal, 3000);

Чтобы исключить автоматическое повторное появление модального окна после того как пользователь уже сам вручную его открыл и посмотрел, в функции открытия модального окна допишем clearInterval():

Код

function openModal() {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
  clearInterval(modalTimerId);
}


Автоматически появляющееся модальное окно при пролистывании страницы сайта


Рассмотрим как можно реализовать автоматически всплывающее модальное окно, если пользователь прокрутил страницу сайта до конца.

Чтобы отслеживать скролл страницы пользователем, используем событие scroll навешивая обработчик события для глобального объекта window

Код

window.addEventListener('scroll', () => {

});

Чтобы определить, когда пользователь долистал страницу до конца, нужно прописать условие, которое будет сравнивать равна ли сумма(или больше) прокрученной части страницы и видимой в данный момент части страницы в окне браузера полной прокрутке страницы:

Код

window.addEventListener('scroll', () => {
  if(window.pageYOffset + document.documentElement.clientHeight >= document.documentElement.scrollHeight) {
  openModal();
  }
});

здесь window.pageYOffset - это прокрученная пользователем часть страницы
document.documentElement.clientHeight - видимая часть страницы в браузере на данный момент
document.documentElement.scrollHeight - величина полной прокрутки страницы

Если написанное нами условие истинное значит пользователь долистал страницу сайта и запустится функция открытия модального окна.

Давайте функционал открытия модального окна при скролле вынесем в отдельную функцию, назовём её showModalByScroll:

Код

function showModalByScroll() {
  if(window.pageYOffset + document.documentElement.clientHeight >= document.documentElement.scrollHeight) {
  openModal();
  }
}

window.addEventListener('scroll', showModalByScroll);

И внутри функции showModalByScroll нужно удалить обработчик события, чтобы модальное окно не появлялось снова и снова при каждой прокрутке страницы до конца:

Код

function showModalByScroll() {
  if(window.pageYOffset + document.documentElement.clientHeight >= document.documentElement.scrollHeight) {
  openModal();
  window.removeEventListener('scroll', showModalByScroll);
  }
}

Полный код нашего скрипта :

Код

window.addEventListener('DOMContentLoaded', function() {

  const modalTrigger = document.querySelectorAll('[data-modal]'),
  modal = document.querySelector('.modal'),
  modalCloseBtn = document.querySelector('[data-close]');

  function openModal() {
  modal.classList.add('show');
  modal.classList.remove('hide');
  document.body.style.overflow = 'hidden';
  clearInterval(modalTimerId);
  }

  modalTrigger.forEach(btn => {
  btn.addEventListener('click', openModal);
  });

  function closeModal() {
  modal.classList.add('hide');
  modal.classList.remove('show');
  document.body.style.overflow = '';
  }

  modalCloseBtn.addEventListener('click', closeModal);

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

  document.addEventListener('keydown', (e) => {
  if(e.code === "Escape" && modal.classList.contains('show')) {
  closeModal();
  }
  });

  const modalTimerId = setTimeout(openModal, 3000);

  function showModalByScroll() {
  if(window.pageYOffset + document.documentElement.clientHeight >= document.documentElement.scrollHeight) {
  openModal();
  window.removeEventListener('scroll', showModalByScroll);
  }
  }

  window.addEventListener('scroll', showModalByScroll);

});

Комментарии к материалу: