Модальное окно для сайта на чистом CSS и JavaScript

Модальное окно для сайта на чистом CSS и JavaScript
Содержание:
  1. Что такое модальное окно?
  2. Подключение CSS и JavaScript-файлов к странице
  3. Создание и настройка модального окна
  4. Методы
  5. События
  6. Примеры
  7. Внутреннее устройство модального окна
  8. Комментарии

В этой статье познакомимся со скриптом, предназначенным для динамического создания модальных окон на сайте. Разберём из чего он состоит, как его подключить и использовать.

Что такое модальное окно?

Модальное окно – это элемент интерфейса, которой визуально представляет собой «всплывающее окно», отображающееся над остальной частью страницы.

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

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

Изображение модального окна:

Вид модального окна, созданного с помощью JavaScript

Оно состоит из заголовка (хедера), основной части и футера.

В заголовке обычно выводят название окна и элемент, с помощью которого его можно закрыть. В основной части располагают содержимое, а в футере – кнопки для выполнения различных действий.

Подключение CSS и JavaScript-файлов к странице

Исходные коды модального окна расположены на GitHub в рамках проекта ui-components в папке modal.

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

HTML
<!-- Подключение CSS-файла -->
<link rel="stylesheet" href="modal.css">

<!-- Подключения JavaScript-файла -->
<script src="modal.js"></script>

Создание и настройка модального окна

Этот скрипт создаёт модальное окно динамически. То есть здесь не нужно вставлять какой-то HTML-код непосредственно на страницу. Реализовано это в коде через класс. Шаблон модального окна содержится в приватном свойстве #template:

JavaScript
class ItcModal {
  #template = '<div class="itc-modal-backdrop"><div class="itc-modal-content"><div class="itc-modal-header"><div class="itc-modal-title">{{title}}</div><span class="itc-modal-btn-close" title="Закрыть">×</span></div><div class="itc-modal-body">{{content}}</div>{{footer}}</div></div>';
  // ...
}

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

JavaScript
const modal = new ItcModal();

При создании окна вы можете сразу же его настроить, для этого в ItcModal необходимо передать аргумент в формате объекта:

JavaScript
const modal = new ItcModal({
  title: 'Заголовок',
  content: '<div>Содержимое модального окна...</div>',
  footerButtons: [
    { class: 'btn btn-close', text: 'Закрыть', action: 'close' },
  ]
});

Ключ title отвечает за заголовок, content – за содержимое, footerButtons – за кнопки в футере окна.

Эти ключи являются не обязательными. Если их не указать, то создастся окно с заголовком «Новое окно», без содержимого и кнопок:

JavaScript
const modal = new ItcModal();

Пример создания модального окна с заголовком «Какой-то текст» и содержимым <p>Мой контент</p>:

JavaScript
const modal = new ItcModal({
  title: 'Какой-то текст',
  content: '<p>Мой контент</p>'
});

В качестве содержимого можно передавать HTML-код.

Добавление кнопок в футер окна осуществляется с помощью ключа footerButtons. Он принимает в качестве значения массив объектов. Каждый объект в этом массиве представляет собой кнопку. Она в свою очередь задаётся посредством ключей text, class и action. С помощью них вы можете кнопке соответственно установить текст, класс и атрибут data-action:

JavaScript
const modal = new ItcModal({
  title: '...',
  content: '<div>...</div>',
  footerButtons: [
    { class: 'btn btn-cancel', text: 'Отмена', action: 'cancel' },
    { class: 'btn btn-ok', text: 'ОК', action: 'ok' }
  ]
});

Методы

Управление созданным модальным окном осуществляется посредством методов:

  • show – открытие;
  • hide – закрытие;
  • dispose – удаление из DOM HTML-элементов модального окна и обработчика события click;
  • setBody – установка основного содержимого;
  • setTitle – изменение заголовка.

Открытие модального окна:

JavaScript
const modal = new ItcModal();
// открыть модальное окно
modal.show();

Скрытие модального окна:

JavaScript
modal.hide();

Изменение заголовка и тела модального окна:

JavaScript
// новый заголовок
modal.setTitle('Текст нового заголовка');
// новое тело
modal.setBody('<div>...</div>');

Уничтожение модального окна:

JavaScript
modal.dispose();

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

События

Если вам нужно выполнить какие-то действия при открытии и закрытии модального окна, то можете воспользоваться событиями:

JavaScript
// при открытии модального окна
document.addEventListener('show.itc.modal', (e) => {
  // e.target - содержит ссылку на модальное окно
  e.target.querySelector('.itc-modal-body').innerHTML = 'Содержимое модального окна, добавленное через <code>show.itc.modal</code>...';
});
// при закрытии модального окна
document.addEventListener('hide.itc.modal', (e) => {
  // e.target - содержит ссылку на модальное окно
  e.target.querySelector('.itc-modal-body').innerHTML = '';
});

Примеры

1. Открытие модального окна при нажатии на кнопку:

HTML
<button id="show-modal">Открыть</button>

<script>
  // создаём модальное окно
  const modal = new ItcModal();
  // при клике по кнопке #show-modal
  document.querySelector('#show-modal').addEventListener('click', () => {
    // откроем модальное окно
    modal.show();
  });
</script>

2. Открытие одного и того же модального окна при нажатии на разные кнопки (определяется через data-атрибут data-toggle="modal"):

HTML
<button data-toggle="modal">Кнопка 1</button>
<button data-toggle="modal">Кнопка 2</button>

<script>
  const modal = new ItcModal({
    content: 'Содержимое модального окна...'
  });
  // при клике на странице
  document.addEventListener('click', (e) => {
    if (e.target.closest('[data-toggle="modal"]')) {
      modal.show();
    }
  });
</script>

3. Заголовок и содержимое модального окна устанавливается из значений data-атрибутов кнопки, посредством которой оно вызывается:

HTML
<button data-toggle="modal" data-title="Заголовок 1" data-content="Содержимое модального окна 1...">Кнопка 1</button>
<button data-toggle="modal" data-title="Заголовок 2" data-content="Содержимое модального окна 2...">Кнопка 2</button>
<script>
  const modal = new ItcModal();
  // при клике на странице
  document.addEventListener('click', (e) => {
    const btn = e.target.closest('[data-toggle="modal"]');
    if (btn) {
      modal.setTitle(btn.dataset.title);
      modal.setBody(btn.dataset.content);
      modal.show();
    }
  });
</script>

4. Обработка события click для кнопок, расположенной в футере модального окна:

HTML
<div class="items">
  <div class="item">
    <img src="/examples/images/car-1.jpg" alt="" data-price="22500" data-name="Audi A5 Coupé">
  </div>
  ...
</div>

<script>
  const modal = new ItcModal({
    title: 'Просмотр изображения',
    content: '<img src="" alt="" style="display: block; height: auto; max-width: 100%;">',
    footerButtons: [
      {class: 'btn btn-delete', text: 'Удалить', action: 'delete'},
      {class: 'btn btn-cancel', text: 'Закрыть', action: 'cancel'}
    ]
  });
  // при клике по документу
  document.addEventListener('click', (e) => {
    const img = e.target.closest('img');
    // если мы кликнули на изображение, то...
    if (img) {
      img.classList.add('active');
      // устанавливаем модальному окну title
      modal.setBody(`<div style="flex: 1 0 50%;">
              <img src="${img.src}" alt="${img.alt}" style="display: block; height: auto; max-width: 100%; margin: 0 auto;">
            </div>
            <div style="flex: 1 0 30%; text-align: center;">
              <div style="font-size: 1.125rem; font-weight:bold;">
                ${img.dataset.name}
              </div>
              Цена:<br><b>${img.dataset.price}</b></div>`);
      modal.show();
    }
    if (e.target.closest('[data-action="cancel"]')) {
      modal.hide();
    }
    if (e.target.closest('[data-action="delete"]')) {
      const img = document.querySelector('img.active');
      img.parentElement.remove();
      modal.hide();
    }
  });
</script>

5. Создание 2 разных модальных окон. Первое модальное окно открывается при нажатии на кнопки с data-атрибутом data-toggle="modal-1", а второе – при клике на data-toggle="modal-2":

HTML
<button data-toggle="modal-1">Открыть окно 1</button>
<button data-toggle="modal-1">Открыть окно 1</button>
<button data-toggle="modal-2">Открыть окно 2</button>
<button data-toggle="modal-2">Открыть окно 2</button>

<script>
  // создадим модальное окно №1
  const modal1 = new ItcModal({
    title: 'Модальное окно 1',
    content: 'Содержимое модального окна 1'
  });
  // создадим модальное окно №2
  const modal2 = new ItcModal({
    title: 'Модальное окно 2',
    content: 'Содержимое модального окна 2'
  });
  document.addEventListener('click', (e) => {
    // при клике по кнопке data-toggle="modal-1"
    if (e.target.closest('[data-toggle="modal-1"]')) {
      // откроем модальное окно №1
      modal1.show();
    }
    // при клике по кнопке data-toggle="modal-2"
    if (e.target.closest('[data-toggle="modal-2"]')) {
      // откроем модальное окно №2
      modal2.show();
    }
  });
</script>

6. Загрузка данных в модальное окно посредством AJAX:

HTML
<a href="#" data-json="/examples/pens/itc-modal/json-1">из json-1</a>
<a href="#" data-json="/examples/pens/itc-modal/json-2">из json-2</a>
...
<script>
  // создадим модальное окно
  const modal = new ItcModal({
    title: 'Модальное окно',
  });
  // при клике по ссылке
  document.addEventListener('click', function (e) {
    const anchor = e.target.closest('a[data-json]');
    if (anchor) {
      e.preventDefault();
      const request = new XMLHttpRequest();
      request.open('GET', 'https://itchief.ru/' + e.target.dataset.json);
      request.send();
      request.onload = () => {
        if (request.status === 200) {
          const data = JSON.parse(request.response);
          let html = '<div style="display: flex; gap: 1rem;"><div style="flex: 1 0 50%;"><img src="{{image}}" alt="" style="display: block; height: auto; max-width: 100%; margin: 0 auto;" width="705" height="440"></div><div style="flex: 1 0 30%; text-align: center;"><div style="font-size: 18px; font-weight:bold;">{{title}}</div>Цена:<br><b>{{price}}</b></div></div>';
          html = html.replace('{{title}}', data.title);
          html = html.replace('{{price}}', data.price);
          html = html.replace('{{image}}', data.image);
          modal.setBody(html);
          // отобразим модальное окно
          modal.show();
        }
      };
    }
  });
</script>

Пример JSON-файла:

JSON
{"title":"Audi A5 Coupé","price":"22500$","image":"https://itchief.ru/examples/images/car-1.jpg"}

7. Работа с событиями, возникающими при открытии и закрытии модального окна:

HTML
<!-- Кнопки для открытия модального окна -->
<button data-toggle="modal">Кнопка 1</button>
<button data-toggle="modal">Кнопка 2</button>
<div class="message"></div>

<script>
  const modal = new ItcModal({
    title: 'Текст заголовка',
    content: '<p>Содержимое модального окна...</p>',
    footerButtons: [
      {class: 'btn btn-2', text: 'ОК', action: 'ok'},
      {class: 'btn btn-1', text: 'Отмена', action: 'cancel'}
    ]
  });
  document.addEventListener('show.itc.modal', () => {
    document.body.style.backgroundColor = '#fff59d';
  });
  document.addEventListener('hide.itc.modal', () => {
    document.body.style.backgroundColor = '#fff';
  });
  document.addEventListener('click', (e) => {
    document.querySelector('.message').innerHTML = '';
    if (e.target.closest('[data-toggle="modal"]')) {
      modal.setBody(`Вы открыли модальное окно при нажатии на <b>${e.target.textContent}</b>`);
      modal.show();
    }
    if (e.target.closest('[data-action="cancel"]') || e.target.closest('[data-action="ok"]')) {
      const text = (e.target.closest('[data-action="cancel"]') || e.target.closest('[data-action="ok"]')).textContent;
      document.querySelector('.message').innerHTML = `Вы завершили действие с модальным окном посредством кнопки <b>${text}</b>`;
      modal.hide();
    }
    if (e.target.closest('.itc-modal-btn-close')) {
      document.querySelector('.message').textContent = 'Вы закрыли окно с помощью крестика';
    }
  });
</script>

Внутреннее устройство модального окна

Код JavaScript модального окна представлен посредством класса ItcModal:

JavaScript
class ItcModal {
  #elem;
  #template = '<div class="itc-modal-backdrop"><div class="itc-modal-content"><div class="itc-modal-header"><div class="itc-modal-title">{{title}}</div><span class="itc-modal-btn-close" title="Закрыть">×</span></div><div class="itc-modal-body">{{content}}</div>{{footer}}</div></div>';
  #templateFooter = '<div class="itc-modal-footer">{{buttons}}</div>';
  #templateBtn = '<button type="button" class="{{class}}" data-action={{action}}>{{text}}</button>';
  #eventShowModal = new Event('show.itc.modal');
  #eventHideModal = new Event('hide.itc.modal');
  #disposed = false;

  constructor(options = []) { 
    // ...
  }

  #handlerCloseModal(e) {
    // ...
  }

  show() {
    // ...
  }

  hide() {
    // ...
  }

  dispose() {
    // ...
  }

  setBody(html) {
    // ...
  }

  setTitle(text) {
    // ...
  }
};

В конструкторе мы создаём DOM-элемент и формируем его HTML-структуру. Ссылку на созданный элемент мы помещаем в приватное свойство #elem. Данное свойство мы будем использовать в других методах ItcModal. Для вставки на страницу модального окна используется метод append.

Приватное свойство #disposed применяется для хранения состояния. По умолчанию оно имеет значение false. Это свойство связано с методом dispose(). При вызове этого метода, модальное окно удаляется со страницы. Но, кроме этого, также удаляется событие, связанное с ним. Для отметки этого действия, свойству #dispose присваивается значение true:

JavaScript
dispose() {
  this.#elem.remove(this.#elem);
  this.#elem.removeEventListener('click', this.#handlerCloseModal);
  this.#disposed = true;
}

После этого действия мы не сможем открыть модальное окно, так как его уже нет. Чтобы это не приводило к ошибкам в коде, в метод show добавлена следующая проверка:

JavaScript
show() {
  if (this.#disposed) {
    // если модальное окно удалено, то завершаем работу
    return;
  }
  this.#elem.classList.add('itc-modal-show');
  this.#elem.dispatchEvent(this.#eventShowModal);
}

При открытии и закрытии модального окна, код генерирует события show.itc.modal и hide.itc.modal с помощью метода dispatchEvent. Сами события находятся в приватных свойствах #eventShowModal и #eventHideModal. Эти события вызываются для this.#elem. Используя их, вы можете очень просто добавить нужную логику при открытии и закрытии модального окна:

JavaScript
document.addEventListener('show.itc.modal', (e) => {
  // e.target - содержит ссылку на модальное окно
  // ...
});
document.addEventListener('hide.itc.modal', (e) => {
  // e.target - содержит ссылку на модальное окно
  // ...
});

Приватный метод #handlerCloseModal является обработчиком события click и используется для закрытия модального окна. То есть он выполняется при нажатии на крестик или при клике на backdrop. Это действие мы вынесли в отдельный метод для того, чтобы потом мы могли удалить его при вызове метода dispose.

Комментарии: 14

ekaterisa
ekaterisa

Добрый день. Спасибо за хорошее решение для вывода модального окна.

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

Добрый день! Внёс изменения в код на Github. Попробуйте сейчас.

ekaterisa
ekaterisa

Спасибо. Если применять 'button' то данная проблема отсутствует. Но хотелось бы вызвать модальное окно используя ссылку

Александр Мальцев
Александр Мальцев

Тогда в обработчике события нужно просто вызвать метод e.preventDefault():

document.querySelector('#show-modal').addEventListener('click', (e) => {
  // отменяем стандартное поведение браузера
  e.preventDefault();
  // открываем модальное окно
  modal.show();
});
igor_khod
igor_khod

Здравствуйте, Александр!

Очень понравился ваша технология модального окна.

Всё работает прекрасно!

Хотел уточнить, мой PhpStorm в файле modal.js (8 строка) зачеркнул функцию evt.initCustomEvent снабдив комментарием:

"Эта функция больше не рекомендуется. События, инициализированные таким образом, должны были быть созданы с помощью метода Document.CreateEvent(). 
Примечание
Больше не используйте этот метод, так как он устарел.
Вместо того, чтобы использовать эту функцию, вместо этого используйте конкретные конструкторы событий, такие как CustomEvent()"

Подскажите что в этой ситуации можно сделать?

Александр Мальцев
Александр Мальцев

Добрый день! Это было нужно для совместимости с IE11. Так как код компонента довольно устарел, переписал его с использованием классов. Код на GitHub обновил, а также переделал примеры в статье. Остальная часть статьи в процессе.

igor_khod
igor_khod

Здравствуйте, Александр!

Новый подход получился компактнее, изящнее и красивее!!! А значит и работать будет лучше. Респект и уважуха! :)

У меня всего 1 вопрос:

Я использую ваше модальное окно для вызова формы обратной связи. При загрузке формы подключаются дополнительные обработчики событий (addEventListener), которые вступают в конфликт с обработчиком событий модального окна, который, в свою очередь, обрабатывается первым.

Посоветуйте - в какую сторону копать? Делегирование событий?

Александр Мальцев
Александр Мальцев

Да, используйте делегирование. Почти все события всплывают. Вешайте обработчик на document или само модальное окно и там уже всё обрабатывайте.

Timito
Timito

При использовании структуры Bootstrap 5 почему-то код не работает вовсе. Не могли бы вы подсказать как это исправить?

Александр Мальцев
Александр Мальцев

Добавил префиксы к классам. Сейчас не должно конфликтовать.

Вячеслав
Вячеслав

Здравствуйте Александр! Я использую первый пример модального окна. Возникла необходимость поместить в него форму входа на сайт. Но при добавлении события onclick в коде:

<a className="login-with uid" href="javascript:" onclick="window.open('https://login.uid.me/?site=emytestsait&ref='+escape(location.protocol + '//' + ('mytestsait.ru' || location.hostname) + location.pathname + ((location.hash ? ( location.search ? location.search + '&' : '?' ) + 'rnd=' + Date.now() + location.hash : ( location.search || '' )))),'uidLoginWnd','width=580,height=450,resizable=yes,titlebar=yes');return false;" rel="nofollow" title="Войти через uID"><i></i></a>
<a className="login-with vkontakte" data-social="vkontakte" href="javascript:" onclick="return uSocialLogin('vkontakte');" rel="nofollow" title="Войти через ВКонтакте"><i></i></a>
<a className="login-with facebook" data-social="facebook" href="javascript:" onclick="return uSocialLogin('facebook');" rel="nofollow" title="Войти через Facebook"><i></i></a>
<a className="login-with yandex" data-social="yandex" href="javascript:" onclick="return uSocialLogin('yandex');" rel="nofollow" title="Войти через Яндекс"><i></i></a>
<a className="login-with google" data-social="google" href="javascript:" onclick="return uSocialLogin('google');" rel="nofollow" title="Войти через Google"><i></i></a>
<a className="login-with ok" data-social="ok" href="javascript:" onclick="return uSocialLogin('ok');" rel="nofollow" title="Войти через Одноклассники"><i></i></a>

Окно не открывается. Код окна я добавляю через modal.js. Помогите пожалуйста решить проблему.

Ксения
Ксения

Александр, добрый день. Подскажите, пожалуйста, нигде не могу найти подходящую информацию. Мне нужен код для нескольких модальных окон на основе jQuery. Объясняю. Есть 6 кнопок, 6 модальных окон с разным заголовком и текстом. При нажатии на первую кнопку должно появляться первое окно и закрываться при нажатии на крестик. При нажатии на вторую кнопку - второе окно и т.д. Подскажите, пожалуйста, где можно поискать что-то подобное? Заранее благодарна!

Александр Мальцев
Александр Мальцев

Привет!

Это можно реализовать самостоятельно.

1) Придумать разметку модальному окну:

<!-- Модальное окно 1 -->
<div id="modal-1" class="modal">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h3 class="modal-title">Название 1</h3>
        <a href="#" class="close" title="закрыть">×</a>
      </div>
      <div class="modal-body">
        Содержимое модального окна 1...
      </div>
    </div>
  </div>
</div>

<!-- Модальное окно 2 -->
<div id="modal-2" class="modal">...</div>
2) Написать стили:
.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1050;
  opacity: 0;
  transition: opacity 400ms ease-in;
  pointer-events: none;
}
.modal.show {
  opacity: 1;
  pointer-events: auto;
  overflow-y: auto;
}
...
3) Написать код на jQuery:
$(document).on('click', '[data-toggle="modal"]', function () {
  var target = $(this).attr('data-target');
  $(target).addClass('show');
  return false;
});
$('.modal .close').on('click', function () {
  $(this).closest('.modal').removeClass('show');
  return false;
});
Готовый пример: перейти
AA
AA

Добрый день, не могли мне подсказать по одному вопросу. У меня есть график, который рисует данные в реальном времени. Берёт он их с запроса ajax. Получается, что каждые три минуты он получает данные и рисует их. Получается волнообразная графика. Теперь мне нужно сделать алерт, чтоб он срабатывал, когда данные ниже определенного уровня. К примеру, данные всегда выше 600, но, если вдруг ниже 500 упадет, чтоб он сработал и вывел текст типа ошибка такая-то. И ещё чтоб можно было свернуть до того момента пока не решилась проблема, и в алерте не нажали исправлена. В простом встроенным алерте такое к сожалению нельзя, а сам затрудняюсь написать свой алерт, может кто то поможет(