Как сделать модальное окно

Обновлено: 28.04.2024

В наше время для различных сайтов нормой стали всевозможные всплывающие модальные окна — popup'ы — для регистрации, авторизации, информационные окна, — всевозможных форм и размеров. Также существует огромное количество плагинов к тому же jQuery для простого и удобного создания таких попапов — тот же Shadowbox, например.

Внешний вид, размеры и оформление таких попапов совершенно разнообразными — с оверлеем, тенюшками, анимациями — всего не счесть. Объединяет их только, пожалуй, тот факт, что обычно они выводятся в самом центре страницы — как по горизонтали, так и по вертикали. И центрирование это производится средствами JS. Я не буду вдаваться в подробности этих расчетов, опишу их лишь вкратце:

HTML-код попапа обычно имеет такую структуру:

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

JS определяет браузер и версию браузера, и на основании этого высчитывает размеры рабочей области и размеры самого попапа (если они не заданы), а затем производятся нехитрые вычисления положения его левого верхнего угла (css-свойства left и top для .popup). Многие плагины также реагируют на изменение размеров страницы, пересчитывая всё это дело каждый раз, с тем, чтобы попап располагался точно в центре рабочей области.

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

Этим и займёмся.

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

Для начала — HTML-код. Структура его будет немного отличаться от кода, указанного выше, почему — об этом ниже в статье; классы элементов останутся теми же:

.popup__overlay position : fixed ;
left : 0 ;
top : 0 ;
width : 100% ;
height : 100% ;
z-index : 999
>
.popup >

Фиксированные размеры

Самый простой вариант. Ничего нового изобретать не нужно:

.popup left : 50% ;
top : 50% ;
width : 400px ;
height : 200px ;
margin-left : -200px ;
margin-top : -100px
>

Отрицательные margin'ы в половину ширины и высоты — банально и скучно, ничего оригинального в этом нет. Идём дальше.

Размеры попапа зависят от содержимого

Сперва — выравнивание по горизонтали — это вроде бы проще. Если попап фиксированной ширины — то достаточно будет следующего:

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

Вертикальное выравнивание. Здесь уже становится интересно. С такой задачей, конечно, без проблем справилась бы таблица или эмуляция таблицы с помощью display: table & display: table-cell, но заставить такое работать в старых IE — себе дороже. Таблица также отпадает — по понятным причинам.

Итак, margin уже отпадает — размеров мы не знаем. Вспоминаем, что же есть из свойств с подобными эффектами. Ага, text-align. Но только для инлайновых элементов. ОК. Кажется, сам Бог велел использовать display: inline-block — блочный элемент, к которому можно было бы применить свойства для инлайновых элементов. С поддержкой этого свойства у всех браузеров тоже всё, можно сказать, в порядке. Код становится примерно таким:

.popup__overlay position : fixed ;
left : 0 ;
top : 0 ;
width : 100% ;
height : 100% ;
z-index : 999 ;
text-align : center
>
.popup display : inline - block ;
vertical-align : middle
>

Остаётся вертикальное выравнивание — нам подойдёт vertical-align. В любой другой ситуации было бы также уместно использовать line-height, но поскольку у нас нет фиксированной высоты страницы (line-height в данном контексте), здесь использовать её нельзя. На помощь приходит один трюк с вертикальным выравниванием элементов неизвестных размеров. Я точно помню, что нашел этот способ на Хабре, но, к сожалению, не смог найти ссылку на тот топик. Заключается этот способ в следующем: добавляется inline-block элемент нулевой ширины и 100%-ой высоты родителя, который «расхлопывает» высоту строки до 100% высоты родителя, то есть до высоты рабочей области страницы. Сделаем это изящнее — вместо лишней разметки воспользуемся псевдоэлементами:

.popup__overlay :after display : inline - block ;
width : 0 ;
height : 100% ;
vertical-align : middle ;
content : ''
>

Осталось добавить полупрозрачное затемнение оверлея — с этим справится rgba. Всё! Теперь положение попапа регулируется только средствами браузера на уровне CSS.

Из замеченных минусов метода — порой возникают глюки с отображением в IE 6-7, в частности, при использовании флоатов.

Повторюсь — мой метод не избавляет от использования JS вообще, он лишь частично оптимизирует скорость работы всплывающих окон за счет переноса части нагрузки с JS на CSS.

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

Уверен, многие хоть раз создавали всплывающее модальное окно. Но задумывались ли вы об определении этого компонента? Как он должен работать?

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

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

Этот список сформирован на основе спецификаций WAI-ARIA, HTML Living Standard и моего личного опыта. И хотя я буду говорить про веб, большинство правил и рекомендаций применимы для модальных окон где угодно.

Определение модального окна

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

Теги и атрибуты

Простейшая реализация кнопки открывающая диалог по его id:

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

  • Chromium — полная поддержка.
  • Firefox — поддержка за флагом.
  • Safari не поддерживает вовсе.

Так что для этих браузеров нужно подгружать polyfill:

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

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

Внешний вид и содержание

Вскользь коснусь внешнего вида.

На небольших экранах диалоговое окно должно занимать 100% его размера. Если ваш диалог будет большим:

  1. Его будет легче "нащупать". Дело в том, что пользователь может взаимодействовать со страницей следующим образом: он водит пальцем по дисплею, а программа чтения с экрана озвучивает то, что в данный момент находится под пальцем.
  2. Пользователю гарантированно не будут озвучиваться элементы "под ним". Иначе, например, VoiceOver на iPad может озвучивать отдельные фрагменты страницы под модальным окном даже "сквозь" оверлей блокирующий доступ указателю.
  3. Вы скроете прокрутку фона на некоторых устройствах при прокрутке контента в диалоговом окне.
  4. Удобнее для одной руки. Если окно растянуто на всю высоту – то у вас есть возможность прижать кнопки управления к нижней части дисплея. Туда намного проще дотянуться одной рукой пользователям современных смартфонов.
  5. Больше места для контента на устройствах с маленьким экраном, таких как iPhone SE.

Заголовок обязателен

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

Настоятельно рекомендуется использовать для заголовка тег - .

Но просто добавить заголовок в диалоговое окно недостаточно. Их нужно ещё и логически "связать". Сделать это можно с помощью атрибута aria-labelledby следующим образом:

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

Статический контент должен быть связан с окном

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

Делается это атрибутом aria-describedby :

Если в вашем диалоговом окне много контента, тогда стоит обернуть его в один и связать элемент диалога уже с ним:

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

Интерактивные элементы связывать не нужно

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

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

Если скомбинировать и статический текст и форму:

Способы закрыть окно

Дополнительно, в зависимости от вашей логики, вы можете позволить пользователю закрыть диалог кликнув за его пределами или нажав Escape (встроено в из коробки).

  1. Не рассчитывайте, что пользовать всегда может нажать на оверлей и так закрыть диалог.
    1. Как я писал ранее, во многих случаях диалоговое окно может занимать всю или большую часть экрана. Таким образом попасть в него может быть сложно или невозможно.
    2. Такой оверлей семантически не считается интерактивным элементом. Он не может быть в фокусе и на него невозможно "нажать" клавишами.

    Простейшая реализация кнопки закрывающей родительский диалог:

    А если вы делаете кнопку с иконкой, то не забывайте про подпись, чтобы передать ёё назначение:

    Поведение фокуса

    При открытии диалога

    Во время открытия диалогового окна фокус должен быть перемещён на элемент внутри него. На какой именно — зависит от содержания.

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

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

    Но есть и несколько исключений:

    1. Запрос подтверждения чего-либо. Если ваш диалог запрашивает у пользователя подтверждения перед выполнением каких-то необратимых действий (удаление чего-то или выполнение финансовых операций), тогда фокус автоматически должен ставится на кнопку "отмены" этих действий, независимо от её расположения.
    2. Ситуации, когда в диалоговом окне много статического контента и первый интерактивный элемент не помещается в видимую область. Проблема тут в том, что в таком случае браузер автоматически проскролит вниз к кнопке в фокусе. Это вынудит пользователя скролить обратно вверх, а потом снова вниз. Для таких случаев есть два подхода:
      1. Переместить или продублировать интерактивные элементы так, чтобы первый из них был в видимой части экрана. Например, выполнить кнопку закрыть в виде крестика и закрепить в верхней части диалогового окна.
      2. Заголовок или первый абзац текста нужно сделать фокусируемым при помощи tabindex="-1" и перемещать фокус на него. Но при этом подходе некоторые программы чтения с экрана могут озвучивать заданный текст дважды: сначала как заголовок и описание окна, а потом как содержание выделенного элемента.

      Управлять куда именно попадёт фокус при открытии модального окна можно с помощью атрибута autofocus :

      Внутри диалога

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

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

      Но этого недостаточно, так как остаётся ещё и навигация клавишами Tab / Shift + Tab . Также это могут быть клавиши громкости на смартфонах или специальные клавиши на дополнительных инструментах подключенных по USB/Bluetooth. Этот способ навигации тоже должен быть заблокирован.

      После попадания фокуса в модальное окно пользователь может перебирать интерактивные элементы внутри этого окна, но не должен выходить за его пределы. Другими словами, такое диалоговое окно работает как ловушка для фокуса. Это поведение встроено в , так что от вас никаких действий не требуется. А вот используя другой элемент с role="dialog" его нужно реализовывать самостоятельно средствами JavaScript.

      При закрытии диалога

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

      Но и тут есть одно исключение: если элемент более не доступен, тогда фокус нужно вернуть туда, откуда наиболее логично для пользователя продолжить работу.

      Пример

      Предлагаю разобрать на примере. Представим систему из трех диалоговых окон:

      1. Сообщает пользователю об наличии подписки. В нем две кнопки: "Условия подписки" и "Подписаться"
      2. Отображается по клику на "Условия подписки". Открывается поверх первого.
      3. Отображается по клику на "Подписаться". Заменяет собой первое.

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

      Итак, у нас есть стартовая кнопка.

      По нажатию на неё открывается первый диалог. Фокус автоматически перемещается на первый интерактивный элемент. А закрытие диалога должно возвращать фокус назад.

      Далее пользователь перемещает фокус на "Условия подписки" и нажимает. Открывается второй диалог поверх первого. Фокус перемещается в него, а возвращаться должен на эту же кнопку в первом диалоге:

      После закрытия второго диалога ваш JavaScript должен вернуть фокус на кнопку "Условия подписки" в первом.

      После чего пользователь нажимает кнопку "Подписаться". По условиям нашей задачи открывается третий диалог. Фокус автоматически перемещается в него. А первый диалог закрывается:

      И вот проблема: третье окно должно вернуть фокус на кнопку в первом, но первое окно больше не доступно. В таких случаях фокус нужно вернуть туда, куда указывал закрытый диалог — на кнопку "Рассылка" с которой пользовать начал.

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

      Помните, как во время установки программы в Windows можно просто нажимать Enter? Так вот это пример хорошей работы с фокусом: каждый раз, при переходе на новый экран в фокус ставится элемент, с которым вы скорее всего будете взаимодействовать — кнопка "Далее" или "Обзор".

      Вёрстка таких окон сначала кажется простой задачей. Модальные окна можно сделать даже без помощи JS только лишь с помощью CSS, но на практике они оказываются неудобными, и из-за маленьких недочетов модальные окна раздражают посетителей сайта.

      В итоге было задумано сделать собственное простое решение.


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

      • Arctic Modal,
      • jquery-modal,
      • iziModal,
      • Micromodal.js,
      • tingle.js,
      • Bootstrap Modal (из библиотеки Bootstrap) и др.

      (в статье не рассматриваем решения на базе Frontend-фреймворков)

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

      Что мы ждём от модальных окон? Отвечая на этот вопрос, я основывался на докладе «Знакомьтесь, модальное окно» Анны Селезнёвой, а так-же на относительно старой статье NikoX «arcticModal — jQuery-плагин для модальных окон».

      Итак, чтобы нам хотелось видеть?

      Дисклеймер: Прежде чем мы рассмотрим подробности, сразу дам ссылку на готовый код получившейся библиотеки (HystModal) на GitHub, а также ссылку на демо+документацию.

      Начнём с разметки.

      1. Разметка HTML и CSS

      1.1. Каркас модальных окон

      Как открыть окно быстро? Самое простое решение: разместить всю разметку модального окна сразу в HTML странице. Затем скрывать/показывать это окно при помощи переключения классов CSS.

      Набросаем такую разметку HTML (я назвал этот скрипт «hystmodal»):

      Сделаем так, чтобы .hystmodal растягивался на всё окно браузера и закрывал собой содержимое страницы. Чтобы этого добиться, установим фиксированное позиционирование в CSS и приравняем свойства top, bottom, left и right к нулю.

      В этом коде сделаны ещё две вещи:

      1. Так как мы хотим центрировать окно внутри страницы, превращаем .hystmodal в flex-контейнер с выравниваем его потомков по центру по вертикали и горизонтали.
      2. Окно может быть больше высоты экрана браузера, поэтому мы устанавливаем overflow-y: auto , чтобы при переполнении возникала полоса прокрутки. Также, для сенсорных экранов (в основном для Safari) нам стоит установить свойство -webkit-overflow-scrolling: touch , чтобы сенсорная прокрутка работала именно на этом блоке а не на странице.

      Теперь установим стили для самого окна.

      Кажется возникли сложности.

      Проблема №1. Если высота окна больше высоты окна браузера, то контент окна будет обрезан сверху.


      Это возникает из-за свойства justify-content: center . Оно позволяет нам удобно выровнять потомков по основной оси (по вертикали), но если потомок становится больше родителя то часть его становится недоступной даже при прокручиваемом контейнере. Подробнее можно посмотреть на stackoverflow. Решение – установить justify-content: flex-start , а потомку установить margin:auto . Это выровняет его по центру.

      Проблема №2. В ie-11 если высота окна больше высоты окна браузера, то фон окна обрезается.

      Решение: мы можем установить flex-shrink:0 потомку – тогда обрезки не происходит.

      Проблема №3. В браузерах кроме Chrome нет отступа от нижней границы окна (т.е. padding-bottom не сработал).

      Сложно сказать баг это браузеров или наоборот соответствует спецификации, но решения два:

      • установить псевдоэлемент ::after после потомка и дать ему высоту вместо padding
      • обернуть элемент в дополнительный блок и дать отступы уже ему.

      Воспользуемся вторым методом. Добавим обертку .hystmodal__wrap . Так мы заодно обойдём и проблему №1, а вместо padding у родителя установим margin-top и margin-top у самого .hystmodal__window .

      Наш итоговый html:

      В код также добавлены некоторые aria и role атрибуты для обеспечения доступности.

      Обновленный код CSS для обертки и окна.

      1.2 Скрываем окно

      Сейчас наше окно всегда видно. Когда говорят о скрытии элементов, первое что приходит на ум это переключать свойство display со значения none до нашего значения flex.

      Но этот подход нас не устроит, ведь свойство display не анимируется. Все переходы дочерних элементов, указанные в свойстве transition, работать не будут.

      Нам поможет другое свойство visibility:hidden . Оно скроет окно визуально, хотя и зарезервирует под него место. А так как все будущие окна на странице имеют фиксированное
      позиционирование – они будут полностью скрыты и не повлияют на остальную страницу. Кроме того, на элементы с visibility:hidden нельзя установить фокус с клавиатуры, а от скрин-ридеров мы уже скрыли окна с помощью атрибута aria-hidden="true" .

      Добавим также классы для открытого окна:

      1.3 Оформление подложки

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

      Просто разместим элемент .hystmodal__shadow прямо перед закрывающим . В будущем, сделаем так, чтобы этот элемент создавался автоматически из js при инициализации библиотеки.

      1.4 Отключение прокрутки страницы

      Когда модальное окна открывается, мы хотим, чтобы страница под ним не прокручивалась.
      Самый простой способ этого добиться — повесить overflow:hidden для body или html, когда окно открывается. Однако с этим есть проблема:

      Проблема №4. В браузере Safari на iOS страница будет прокручиваться, даже если на тег html или body повешен overflow:hidden .
      Решается двумя способами, либо блокированием событий прокрутки (touchmove, touchend или touchsart) из js вида:

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

      ps: можно конечно применить библиотеку scroll-lock, в которую заложено это решение, но в статье было решено воспользоваться другим вариантом.

      Другое решение – основано частично на CSS. Пусть когда окно открывается, на элемент будет добавляться класс .hystmodal__opened :

      Благодаря position:fixed , окно не будет прокручиваться даже в safari, однако здесь тоже не всё гладко:

      Проблема №5. При открытии/закрытии окна страница прокручивается в начало.
      Действительно, это происходит из-за изменения свойства position, текущая прокрутка окна сбрасывается.

      Для решения, нам нужно написать следующий JS (упрощенно):

      При открытии:

      При закрытии:

      Отлично, приступим к JavaScript коду.

      2. Код JavaScript

      2.2 Каркас библиотеки

      Нам нужна совместимость со старыми браузерами включая IE11 поэтому нам нужно выбрать из 2 вариантов кодинга:

      • Разрабатывать на старом стандарте ES5, и использовать только те фичи, которые поддерживают все браузеры.
      • Применить современный синтаксис ES6, но подключить транспайлер Babel, который автоматически преобразует код для всех браузеров и встроит необходимые полифилы.
        Было принято решение использовать второй вариант, с прицелом на будущее.
        Приступим.

      Основа нашей библиотеки единственный класс HystModal . Ниже я приведу скелет кода с комментариями, а потом добавим остальной функционал.

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

      Проблема №6: если в браузере есть фиксированный скроллбар (который влияет на ширину страницы), то при открытии/закрытии окна происходит сдвиг контента, когда полоса прокрутки то появляется то пропадает.

      Действительно – скроллбар пропадает и контент страницы перераспределяется. Чтобы решить эту проблему, можно добавить отступ справа к тегу html, равный ширине скроллбара когда он пропадает.

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

      Дополним метод _bodyScrollControl()

      Почему код метода close() упрощён? Дело в том, что просто убирая классы CSS у элементов, мы не можем анимировать закрытие окна.

      Проблема №7. При закрытии окна, свойство visibility:hidden применяется сразу и не даёт возможности анимировать закрытие окна.

      Причина этого известна: свойство visibility:hidden не анимируется. Конечно, можно обойтись без анимации, но, если она нужна, сделаем следующее.

      • Создадим дополнительный CSS-класс .hystmodal—moved почти такой-же как и .hystmodal--active
      • Затем при закрытии сначала добавим этот класс к окну и повесим обработчик события «transitionend» на модальном окне. Затем удалим класс `.hystmodal—active , таким образом вызывая css-переход. Как только переход завершится, сработает обработчик события «transitionend», в котором сделаем всё остальное и удалим сам обработчик события.

      Ниже: новая версия методов закрытия окна:

      Вы заметили, что мы создали ещё один метод _closeAfterTransition() и перенесли основную логику закрытия туда. Это нужно, чтобы удалить обработчик события transitionend после закрытия окна, ведь в метод removeEventListener необходимо передать именно ту функцию, которую мы привязывали.

      Кроме того, если анимация не будет нужна, можно просто вызвать this._closeAfterTransition() не вешая его на событие.

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

      2.2 Закрытие окна по клику на оверлей

      Нам нужно обработать ещё одно событие – закрытие окна по клику на элемент подложки .hystmodal__wrap . Мы можем повесить обработчик клика на документ для делегирования события как при открытии и проверить что событие произошло на .hystmodal__wrap примерно так:

      Это будет работать, но есть один малозаметный недостаток.

      Проблема №8. Если кнопку мыши нажать внутри окна, а отпустить за его пределами (над подложкой), окно закрывается.

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

      Окно закрывается потому что по спецификации, если нажатие и отпускание мыши были на разных элементах, то событие click сработает на самом ближайшем общем для них элементе, а у нас это как раз .hystmodal__wrap .

      Мы могли бы решить это изменением html, добавляя ещё один div сразу после .hystmodal__window и размещая его визуально под окном. Но нам бы не хотелось добавлять лишний пустой div ещё сильнее усложняя разметку.

      Мы можем разбить наш addEventListener на два отдельных обработчика: для событий mousedown и mouseup и будем проверять чтобы оба события происходили именно на .hystmodal__wrap . Добавим новые обработчики событий в наш метод eventsFeeler()

      2.3 Управление фокусом

      У нас заготовлено два метода для управления фокусом: focusContol() для переноса фокуса внутрь окна и обратно при его закрытии, а также focusCatcher(event) для блокирования ухода фокуса из окна.

      Решения для фокуса были реализованы аналогично js-библиотеке «Micromodal» (Indrashish Ghosh). А именно:

      1. В служебный массив сохраним все css селекторы на которых может быть установлен фокус (свойство помещаем в init()):

      2. В методе focusContol() находим первый такой селектор в окне и устанавливаем на него фокус, если окно открывается. Если же окно закрывается – то переводим фокус на this.starter :

      3. В методе focusCatcher() находим в окне и превращаем в массив коллекцию всех элементов на которых может быть фокус. И проверяем, если фокус должен был выйти на пределы окна, то вместо этого устанавливаем фокус снова на первый или последний элемент (ведь фокус можно переключать как по Tab так и по Shift+Tab в обратную сторону).

      Результирующий код метода focusCatcher:

      По сути мы реализовали все необходимое для успешного создания модальных окон, осталось ещё несколько дел:

      Проблема №9. В IE11 не работают методы Element.closest() и Object.assign() .

      Для поддержки Element.closest, воспользуемся полифилами для closest и matches от MDN.

      Можно их вставить просто так, но так как у нас проект всё равно собирается webpack, то удобно воспользоваться пакетом element-closest-polyfill который просто вставляет этот код.

      Для поддержки Object.assign , можно воспользоваться уже babel-плагином @babel/plugin-transform-object-assign

      3. Заключение и ссылки

      Повторяя начало статьи, всё изложенное выше, я оформил в маленькую библиотеку hystModal под MIT-лицензией. Вышло 3 кБ кода при загрузке с gzip. Ещё написал для неё подробную документацию на русском и английском языке.

      Что вошло ещё в библиотеку hystModal, чего не было в статье:

      • Настройки (вкл/выкл управление фокусом, варианты закрытия, ожидание анимации закрытия)
      • Коллбеки (функции вызывающиеся перед открытием окна и после его закрытия (в них передаётся объект модального окна))
      • Добавлен запрет на какие-либо действия пока анимация закрытия окна не завершится, а также ожидание анимации закрытия текущего окна перед открытием нового (если окно открывается из другого окна).
      • Оформление кнопки-крестика закрытия в CSS
      • Минификация CSS и JS плагинами Webpack.

      Если вам будет интересна эта библиотека, буду рад звёздочке в GitHub, или напишите в Issues о найденных багах. (Особенно большие проблемы, наверное, в грамматике английской версии документации, так как мои знания языка пока на начальном уровне. Связаться со мной также можно в Instagram

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

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

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

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

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

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

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

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

      Загрузка и установка модального окна

      Проект модального окна расположен на GitHub. Перейти к нему можно по этой ссылке.

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

      Как создать и вызвать модальное окно

      Эта реализация модального окна не требует непосредственного размещения его HTML кода на странице . Это выполняется программно.

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

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

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

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

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

      Этот код создаст модальное окно без футера, с пустым содержимым и заголовком «Новое окно».

      Но функция $modal не только создаёт модальное окно в DOM, но также предоставляет методы для управления им.

      Для этого нужно создать переменную и присвоить ей результат выполнения функции $modal .

      В эту созданную переменную будет помещён объект (а точнее ссылка на него), имеющий следующие методы:

      • show – для отображения модального окна;
      • hide – для скрытия модального окна;
      • destroy – для удаления модального окна из DOM и связанных с ним обработчиков событий;
      • setContent – для установки контента;
      • setTitle – для установки заголовка.

      Эти методы предназначены для взаимодействия с созданным окном. Они позволяют его открыть, закрыть, изменить ему контент и др.

      Рассмотрим, как работать с этими методами на примерах.

      Например, метод show используется когда вам необходимо показать (открыть) модальное окно:

      Метод hide применяется для его скрытия:

      Методы setContent и setTitle предназначены соответственно для изменения контента и заголовка модального окна после его создания.

      В возвращаемом объекте также есть метод destroy . Его необходимо использовать только когда вам необходимо полностью удалить модальное окно из DOM, а также связанные с ним события:

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

      Примеры использования скрипта для создания модальных окон

      1. Пример кода, выполняющий открытие модального окна при нажатии на определённую кнопку.

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

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

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

      5. Пример, в котором создано 2 разных модальных окна. Первое модальное окно открывается при нажатии на одни элементы, а второе – при клике на другие:

      6. Пример всплывающего окна, данные в которое загружаются с использованием AJAX:

      Пример содержимого файла «json-1»:

      7. Этот пример содержит код для обработки различных событий, связанных с модальном окном и кнопками, расположенными в нём:

      Описание скрипта модального окна

      В этом разделе приведена информация для тех, кто хочет более подробно разобраться с тем, как работает это модальное окно.

      Её JavaScript код представлен посредством функции $modal :

      В качестве результата эта функция возвращает объект, состоящий из 5 методов. Они позволяют нам выполнять различные действия над созданным модальным окном. Назначение каждого метода, а также различные примеры как их использовать мы уже подробно рассмотрели выше. Здесь мы более подробно разберём внутренние переменные и функции $modal .

      В $modal имеются следующие переменные _elemModal , _eventShowModal , _eventHideModal , _hiding , _destroyed , _animationSpeed и функции _createModal , _showModal , _hideModal , _handlerCloseModal .

      Функция _createModal предназначена для формирования HTML-кода модального окна (DOM структуры) и добавления её на страницу. В качестве результата она возвращает ссылку на базовый элемент этого модального окна. Т.к. нам эта ссылка нужна в других частях $modal , то сохраним её в переменную _elemModal :

      Переменные _eventShowModal и _eventHideModal применяются для хранения созданных нами кастомных событий «show.modal» и «hide.modal». Событие «show.modal» мы будем вызывать при открытии модального окна, а «hide.modal» – при закрытии. Эти события будем генерировать для объекта document . Используя их, вы можете очень просто добавить свою логику при открытии и закрытии модального окна:

      Переменные _hiding и _destroyed используются для хранения состояний. Первая применяется для индикации процесса скрытия модального окна. Она имеет значение true во время скрытия окна, в остальных моментах - false . Вторая переменная хранит true или false , в зависимости от того, удалены ли DOM элементы модального окна со страницы и связанные с ним события или нет.

      Переменная _animationSpeed используется для указания времени длительности процесса скрытия модального окна (в миллисекундах).

      Функция _showModal предназначена для включения отображения модального окна на странице, а _hideModal – для его скрытия.

      Функция _handlerCloseModal используется в качестве обработчика события «click» для документа и выполняет скрытие модального окна при нажатии на кнопку его закрытия или вне его.

      Как создать модальное окно на чистом CSS

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

      В большинстве случаев модальное окно создают на JavaScript. Но его можно создать не только с помощью JavaScript, но и посредством только HTML5 и CSS3.

      Демо модального окна

      Демонстрацию всплывающего окна, работающего только на HTML5 и CSS3, вы можете посмотреть здесь:

      HTML и CSS код модального окна

      HTML разметка модального окна:

      Ссылка, с помощью которой осуществляется открытие модального окна:

      CSS модального окна:

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

      Если вам необходимо убрать скролл страницы после отображения модального окна, то к элементу body нужно добавить CSS-свойство overflow со значением hidden . А после скрытия модального окна данное свойство убрать у элемента body . Данное действие можно осуществить только с помощью JavaScript:

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

      Здравствуйте. А как сделать, чтобы нижний край модального окна не «уезжал» за границы видимой области в случае, если много контента?

      Добрый день, а как можно настроить чтоб модальное окно появлялась автоматом, например у меня есть ajax запрос там к примеру данные всегда выше 600, если будет 500, то сработает модальное окно, можно так настроить?

      Доброго времени суток
      Я дублирую html блок с разным описанием, но отображается содержание только первого блока. Можете, пожалуйста, подсказать как решить эту проблему?
      Заранее спасибо

      Привет!
      Тут нужно просто установить разным модальным окнам разные id:
      После этого указать какая ссылка какое модальное окно должна открывать:
      Песочница: открыть пример

      Вам необходимо при нажатии на ссылку отменить стандартное действие браузера, т.е. вызвать метод preventDefault:

      В этом случае вызывать окно следует с помощью JavaScript, либо убирать preventDefault и прописывать полный URL (это у вас из-за того, что в HTML коде имеется атрибут base).

      Здесь тогда нужно прописывать полный путь к странице, а не [[++site_url]].
      Второй вариант — это сделать через JavaScript, как предлагал выше (открыть пример).
      Для этого в CSS вставляем следующее правило:
      В JavaScript добавляем класс «open» при нажатии на ссылку и удаляем его при нажатии на кнопку «Закрыть»:
      А как атрибут base влияет на SEO? Не встречал информации на эту тему.

      Понял, вы хотите добавить к формам какой-то опозновательный признак. Тогда можно так:
      Теперь у вас в каждой форме будет находится скрытое поле с name=«type» и значением равное name формы:

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

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

      формы неработали когда я просто добавил &submitVar=`dopolnitform` а когда добавил название dopolnitform в скрытое поле то заработало как прежде с отправлением всех форм
      убераю это скрытое поле и форма опять не работает. Может у submit должно быть одинаковое имя с &submitVar=`dopolnitform`

      поставил код сейчас и в остальных &submitVar перестал работать. наверное еще снопке button поле name добавлять.

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

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

      Можно просто кнопку с type="submit" поместить в форму (

      .
      ), в этом случае будет отправляться только эта форма.

      вот кнопка, в ней есть type=«submit» и не работает. в остальных формах без джава скрипта работает &submitVar=`content` на двух формах. а с джава скриптом перестал работать &submitVar, у меня формы на Formit может поэтому не работает type=«submit»

      все работает спасибо огромное, только в формах на сайте перестала работать функция в форме &submitVar=`pred` она помогала отличать формы друг от друга, а сейчас при нажатии на одну кнопку в форме отправляются все формы со страницы. Оно добавляло в скрытое поле найм `pred`вот в это поле

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

      1) Почему если поместить внутри ссылки картинку, то окно не появляется, только страница тускнеет?
      2) не закрывается окно по клику на X, ( я пользую вариант из комментария где много окон)

      Добрый день, Александр!
      Я дублирую html блок под каждую новость, чтобы на главной любая аннотация переходила в полную (по Вашему примеру модального окна), но каждая из них лишь отображает содержание первого блока!
      Пожалуйста, подскажите, как реализовать под каждую новость!
      Спасибо!

      Подскажите, пожалуйста, как Вам удалось это сделать? Я столкнулась с точно такой же проблемой. Заранее благодарю

      Судя по Вашей песочнице (вверху прикреплённой), вместо «openmodal» используйте, например, индексы 1,2,3 и т.д. под каждую новость:

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

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

      Добрый день! Сделайте эту ситуацию (модальное окно с селекторами) в какой-нибудь песочнице и укажите ссылку на неё.

      Подскажите пожалуйста, если планируется размещать слайдер в модальном окне, как предпочтительней на CSS или на яваскрипт делать модальное окно.

      Без CSS вам в любом случае не обойтись. Т.к. это единственная возможность, которая у нас есть для стилизации элементов в браузере. А вот использовать вам дополнительно JavaScript или нет, никто кроме вас не знает. Это зависит от функционала. Если вас будет устраивать то, что у вас получится только с использованием CSS, то тогда JavaScript не используйте. А если нет, то тогда с JavaScript.

      Поэтому, когда у вас есть возможность создать что-то без JavaScript и оно вас будет полностью устраивать, то JavaScript использовать не надо.

      Привет!
      Если вы хотите доработать пример с JS, то нужно к ссылкам добавить какой-нибудь признак, который будет определять, что они предназначены для открытия модального окна. Например, атрибут data-toggle=«modal». А атрибут data-target использовать для указания селектора на то модальное окно, которое эта ссылка должна открывать.
      Например:
      Далее нужно переработать JavaScript код, например, следующим образом:
      Ссылка на пример: открыть

      Спасибо большое! Особенно, за разжевывание информации) так понимаешь, что и как работает! еще раз спасибо!

      Здравствуйте! Спасибо! Попробуйте перегрузить страницу с использованием Ctrl + F5.
      У вас в коде ошибка:

      И вот еще возник вопрос, модальное окно появляется в истории окон %) Можно его не записывать туда (убрать из истории)? А то по стрелке «назад» открывается, чтобы Вы думали?!)) — модальное окно, а не предыдущая страница

      Это пример реализации на чистом CSS. Чтобы этого не было можно просто немного поменять логику и добавить чуть-чуть JavaScript кода. Пример модального окна открывающегося и закрывающего с использованием JavaScript.

      Благодарю, получилось )) В чем прикол? И как можно быть уверенным в том, что у пользователя не возникнет такого прочтения кода?

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

      не понимаю какое событие влияет на закрытие окна? в теге а есть ссылка на который в стилях не отмечен, зачем она?

      Читайте также: