Модальное окно. Небольшой блок, появляющийся на экране, чтобы сообщить вам что-то важное. Какие с ним могут возникнуть сложности? Хм, нельзя сказать, что слишком суровые, но могут возникнуть. В этой статье я изложу некоторое количество соображений и хитростей о том, как сделать все правильно. Приступим.

Расположение в DOM

Я обычно размещаю модальное окно перед закрывающим тегом </body>.

 <div class="modal" id="modal"></div>

</body>

</html>

Это делается ради удобства применения стилей. Модальное окно проще позиционировать, когда у вас уже есть готовое тело страницы, а не неизвестный набор родительских элементов, у каждого из которых может быть свой контекст позиционирования.

Насколько это подходит для работы со скринридерами? Я не эксперт в области доступности, но я слышал, что модальные окна вызывают сложности.

Вот, что говорит Роб Додсон:

Каждый, кто когда-либо пытался сделать модальное окно доступным, знает, что несмотря на внешнюю безобидность, модальные окна имеют решающее значение для доступности сайта, это как битва с боссом в игре. Они разжуют вас и выплюнут. Например, в правильно сделанном модальном окне должны быть реализованы следующие возможности:

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

Роб предлагает демо невероятно доступного модального окна. Есть еще недавний пример Ноа Бло и Николаса Хоффмана. Также подойдет ARIA Live Regions.

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

Центрирование

Для начала ознакомьтесь с полным руководством по центрированию в CSS (русский перевод).

Я использую один из моих любимых трюков. С его помощью можно центрировать блок вертикально и горизонтально, не зная его ширины или высоты.

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

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

Центрированное модальное окно

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

Фиксированное позиционирование

Обратите внимание. что мы используем position: fixed;. Смысл этого в том, что, если пользователь прокрутит страницу и вызовет модальное окно, оно будет отцентрировано и видимо для пользователя, независимо от степени прокрутки.

В общем, я считаю, что фиксированное позиционирование в наши дни безопасно, даже на мобильных устройствах. Но если вы знаете, что вам придется сталкиваться со старыми мобильными устройствами, фиксированное позиционирование может стать проблемой и тогда вам придется использовать что-то вроде position: absolute; с принудительным скроллингом вверх страницы. Или что-то еще, тестируйте, в общем.

Ширина

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

.modal {

  /* код, который уже был выложен выше*/

  width: 600px;
  
}

Это опасный подход. Мы получим то, что нам надо на больших экранах, но существует множество устройств, экран которых не достигает и 600 пикселей в ширину.

Проблемы модального окна с фиксированной шириной

Это легко исправить с помощью свойства max-width:

.modal {

  /* код, который уже был выложен выше */

  width: 600px;
  max-width: 100%;
  
}

Модальное окно с max-width=100%

Высота

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

Неудачная обрезка модального окна по высоте

Нас опять спасет задание максимума, в данном случае max-height:

.modal {

  /* код, который уже был выложен выше */

  height: 400px;
  max-height: 100%;
  
}

Правильное модальное окно

Переполнение

Итак, раз мы всерьез озадачились высотой модального окна, нам надо подумать и о его возможном переполнении. Есть соблазн использовать свойство overflow непосредственно на .modal, но здесь возникает пара проблем:

  • у нас может быть желание обойтись без скроллинга в некоторых местах;
  • переполнение обрезает box-shadow, что может нам не подойти.

Я думаю использовать для этого внутренний контейнер:

<div class="modal" id="modal">

  <!-- то, что не прокручивается -->

  <div class="modal-guts">

    <!-- то, что прокручивается -->

  </div>

</div>

Для того, чтобы внутренний блок .modal-guts прокручивался, ему нужна высота. Это решается несколькими способами. Один из них это позиционирование этого блока так, чтобы он покрывал модальное окно целиком с последующим добавлением свойства overflow:

.modal-guts {

  /* код, который уже был выложен выше  */

  /* расположение над модальным окном */
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

  /* отступы при необходимости */
  padding: 20px 50px 20px 20px;

  /* прокрутка */
  overflow: auto;
  
}

Образец прокрутки модального окна

Кнопки

Цель модального окна это форсировать требуемое действие, прежде чем будет сделано что-либо еще. Если вам не надо форсировать действие, вам стоит использовать другой элемент пользовательского интерфейса вместо модального окна. И, конечно, нужен какой-нибудь способ выйти из модального окна. Традиционно используются кнопки опций (типа “удалить/отменить”), еще более распространена кнопка закрытия. Ей мы и займемся.

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

Модальное окно с очевидной кнопкой закрытия

Наложение модального окна

Модальное окно часто сопровождается полноэкранным слоем-наложением. Это полезно по ряду причин:

  • затемняется (или иным способом заглушается) остальная часть экрана, вынуждая пользователя сначала разобраться с модальным окном;
  • наложение можно использовать для предотвращения нажатий и иных взаимодействий с содержимым вне модального окна;
  • наложение можно использовать в качестве очень большой кнопки закрытия модального окна (или кнопки “отмены” и иных безопасных действий).

Типичные разметка и стили для этого:

<div class="modal" id="modal">
  <!-- содержимое модального окна -->
</div>
<div class="modal-overlay" id="modal-overlay">
</div>
.modal {

  /* код, который уже был выложен выше */

  z-index: 1010;

}
.modal-overlay {

  /* рекомендация:
     не зацикливайтесь на числе "1000", вместо этого используйте свою документированную систему для z-index и не забывайте следовать ей. Это число должно быть достаточно высоким в вашей системе.
  */
  z-index: 1000;

  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;

}

Специальный класс для закрытия окна, а не для открытия

Мне казалось заманчивой идеей скрыть класс .modal по умолчанию с помощью display: none;. А для открытия добавить класс .modal.open со стилями { display: block; }.

Вы заметили display: block;, не так ли? Я вижу здесь проблему. Правило display: none; очень полезно, так как скрывает модальное окно как чисто визуально, так и от вспомогательных технологий. Его проще применить для замены существующего свойства display, чем действовать наоборот, угадывая значение display, заменяющее none. Ведь ваше модальное окно может использовать display: flex илиdisplay: grid;, или что-либо еще. Различные варианты модальных окон могут использовать то, что им нужно, не беспокоясь, что их сбросят на display: block;.

.modal {

  /* например, так... */
  display: flex;  

}
.modal.closed {
 
  display: none;

}

Открытие/закрытие окна

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

var modal = document.querySelector("#modal");
var modalOverlay = document.querySelector("#modal-overlay");
var closeButton = document.querySelector("#close-button");
var openButton = document.querySelector("#open-button");

closeButton.addEventListener("click", function() {
  modal.classList.toggle("closed");
  modalOverlay.classList.toggle("closed");
});

openButton.addEventListener("click", function() {
  modal.classList.toggle("closed");
  modalOverlay.classList.toggle("closed");
});

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

See the Pen Considerations for Styling a Modal by Chris Coyier (@chriscoyier) on CodePen.