Чем модальные окна отличаются от блокирующих окон

Обновлено: 13.05.2024

Клиентские приложения все чаще используются при помощи веб-клиента, что предъявляет определенные требования к коду. В этой статье я постараюсь рассказать о модальности в 1С — когда и почему следует от нее отказаться и как это сделать.

Общая информация

В начале рассмотрим общие вопросы связанные с модальными окнами в 1С.

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

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

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

Когда следует отказаться от модальности?

Разумеется отказ от модальности необходим далеко не всегда.

Во-первых, режим работы интерфейса без использования модальности появился в версии технологической платформы 8.3.3.721 (от 06.09.2013) — следовательно, при использовании предыдущих версий технологической платформы, не нужно задумываться об отказе от модальности.

Режим использования модальности

Режим использования модальности

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

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

Отказ от модальности в любой серьезной конфигурации (существующей и использующей модальные окна) — дело непростое. В значительном количестве случаев будет недостаточно заменить один метод на другой — может потребоваться изменение значительной части интерфейса прикладного решения.

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

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

Непосредственно при отказе от модальности очень поможет механизм рефакторинга о котором подробно написано в отдельной статье.

Соответствие синхронных методов асинхронным аналогам (на момент написания статьи) можно посмотреть тут.

Практические примеры

В качестве практических примеров рассмотрим основные приемы работы с блокирующими окнами.


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

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


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

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


Язык модальных окон


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

  • Диалог — в пользовательском интерфейсе — это «разговор» между системой и пользователем.
  • Модальность — особое состояние системы, когда одна и та же система имеет разные интерфейсы. Каждая модальность может сопровождаться различными действиями, или одно и то же действие может приводить к различным результатам в зависимости от модальности системы.
  • Оверлей — окно, которое отображается поверх страницы. Обычно окно оверлея значительно меньше страницы под ним.
  • Скрим/лайтбокс — временный эффект, применяемый к страницам: их содержимое становится менее заметно/более затемнено; собственно, отсюда и название «лайтбокс» (световой куб) — оно описывает визуальный эффект, фокусирующий внимание пользователя на содержимом оверлея.

Типы модальных окон


Nielsen Norman Group создали удобную схему, на которой показаны основные отличия между типами модельных окон:



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

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

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


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


Когда следует использовать модальность

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

«Модальность создает фокусировку, не давая людям заниматься другими делами, прежде чем они не завершат задачу или не отклонят вызванное в модальном окне представление» — Apple

Google советует использовать модальные экраны(диалоговые окна) для…

«Критически важной информации, которая требует конкретной пользовательской задачи, решения или подтверждения» Google

Когда не следует использовать модальность

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

Когда следует использовать немодальное окно

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

Устранение ошибки «Использование в данном режиме модальных окон запрещено» в системе 1С

Эта разновидность ошибки стала возникать после того, как 1С была полностью переведена на новый интерфейс, относящийся к платформе 1С 8.3 – «Такси». Связано это с тем, что разработчиками в ней была включена возможность работы с окнами, однако, она была реализована без режима модальности.


После того, как в режиме «Конфигуратор» открывается информационная база системы, нажимаем правую кнопку мышки, а затем выбираем команду «Свойства». Это позволяет увидеть все свойства конфигурации установленной на компьютере системы. После прокрутки линейки вниз, перед пользователем появляется раздел «Совместимость», в пределах поля которого и расположен тот параметр режима, что его в первую очередь интересует. Кроме того, там перечисляются и возможные варианты в плане выбора: их в общей сложности три, и это может быть «Использовать», «Использовать с предупреждением», либо же «Не использовать».


Затем, в обязательном порядке потребуется сохранить произведенные в конфигурации изменения, после чего и обновить их. Та ошибка, о которой шла речь ранее, возникает, если установлена отметка о недопустимости использования режима модальности. Эта возможность возникла, начиная с платформы версии 8.3.3.721, релиз которой состоялся еще в сентябре 2013-го года. Другими словами, те пользователи системы, которые до настоящего момента по каким-либо причинам все еще продолжают работать с более старыми версиями платформы, могут не отказываться от использования модальности. Что же касается других версий, что бы устранить всякую возможность появления окна с ошибкой, допускается простая установка посредством использования вкладки «Использовать».


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

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

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

Хотите получать подобные статьи по четвергам?
Быть в курсе изменений в законодательстве?
Подпишитесь на рассылку

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

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




Начнем это исследование со следующего смелого утверждения:

Существует два типа экранов:

  1. Модальные экраны
  2. Немодальные экраны

Что такое «модальный экран»?

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

  • Полноэкранные модальные виды
  • Всплывающие окна
  • Поп-апы
  • Лайтбоксы

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

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

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



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

Основное различие заключается в способе взаимодействия с каждым из окон. В то время как немодальный экран позволяет пользователям вернуться к родительскому экрану, модальный экран требует, чтобы пользователи завершили определённое действие, прежде чем вернуться в главное окно (такое как нажатие кнопки «Сохранить» в нашем примере) или отменить текущее действие, вызвавшее модальное окно.

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

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

Почему следует использовать модальность?


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

«Модальность создает фокусировку, не давая людям заниматься другими делами, прежде чем они не завершат задачу или не отклонят вызванное в модальном окне представление» — Apple

Когда следует использовать модальность?

Теперь, когда мы знаем, как выглядит модальный экран. Как же сравнить его с немодальным экраном и какова его цель? Прежде всего мы должны спросить себя: «В какой ситуации мы должны его использовать?»

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

Упрощенный пользовательский поток нашего приложения может выглядеть следующим образом: пользователь открывает приложение и входит в одну из нескольких доступных вкладок (наша база данных о котятах), затем нажимает на одного из котят (входит в подробное представление одного котёнка) и затем нажимает на раздел комментариев (входит в раздел комментариев представления котёнка).



Пользовательский поток приложения «Purrrfect»

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

А теперь надо понять, какой из этих экранов является модальным, а какой нет? Классификация в данном случае вызывает сложность, но вот моё личное эмпирическое правило:

«Автономный процесс» — это конкретное действие, которое имеет четкую начальную и конечную точку в процессе.

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

Google формулирует это правило следующим образом:

Используйте модальные экраны (диалоги) для отображения «критической информации, которая требует конкретной пользовательской задачи, её решения или подтверждения» — Google

В случае нашего приложения «Purrrfect» это означает, что основной поток пользователя (используемый для исследования приложения) не является модальным. Однако специальные ограниченные по времени действия, такие как добавление котят, редактирование котят и написание комментариев, являются модальными.


Однако верно и следующее утверждение: если два разных действия, таких как «Отмена» и «Сохранить», не имеют смысла на вашем модальном экране (потому что они вызовут одно и то же действие), вы можете переключиться на немодальное представление. В этом случае основная навигация (например, панель вкладок) также должна оставаться видимой на экране.

Давайте вернемся к нашему приложению. Возможный интерфейс для «Purrrfect» может выглядеть так:



Пользовательский интерфейс «Purrrfect»

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

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

Минимизируйте использование модальности. Как правило, люди предпочитают взаимодействовать с приложениями нелинейными способами. Рассматривайте возможность создания модального контекста только в тех случаях, когда важно привлечь чье-либо внимание, когда задача должна быть завершена или прекращена, чтобы продолжить использование приложения или сохранить важные данные. — Apple

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

Как следует использовать модальность?

К настоящему времени надеюсь что возникло общее понимание того, когда использовать модальность. Остается только один вопрос: «Как мы спроектируем модальность?». Вот быстрый чеклист для использования модальных экранов:

  • Всегда показывайте кнопку закрытия/скрытия модального окна (или «отмена», «отменить», «свернуть») в верхней панели навигации. Когда пользователь теряется, он может легко закрыть такое окно и вернуться на уровень приложения, откуда было вызвано модальное окно.
  • Кнопки отмены на iOS и Android чаще всего располагаются в верхней левой части панели навигации. Android предпочитает иконку закрытия «X», в то время как iOS предпочитает кнопку с надписью «Отмена». Тем не менее, кнопки иконок также довольно распространен и в iOS.
  • Кнопки подтверждения действия на модальном окне в iOS и Android по умолчанию расположены в верхней правой части панели навигации. Тем не менее, это размещение может быть недоступным для пользователя на устройствах с большой диагональю. Поэтому фиксированное плавающее размещение такого в нижней части экрана или в конце страницы может быть неплохим альтернативным решением.

Многоступенчатые модальные окна

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

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


Заключение


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

Вёрстка таких окон сначала кажется простой задачей. Модальные окна можно сделать даже без помощи 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

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