Как использовать html-элемент dialog

Posted by

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

В своей работе я часто создаю собственные или использую уже готовые UI-компоненты. Проблема с такими компонентами заключается в том, что они часто ограничены определённым фреймворком, и их реализация требует написания сложной нестандартизированной логики. В течение долгого времени для базовых UI-компонентов, таких как диалоговые окна, использовались самописные решения, а в тяжёлых случаях и встроенные в JavaScript методы alert(), prompt() и confirm().

Отличная новость в том, что такой компонент можно реализовать с использованием нативного HTML-элемента <dialog>, который встроен в стандарт HTML5 и работает одинаково во всех современных браузерах.

В статусе рабочего черновика W3C тег <dialog> появился в мае 2013-го года вместе с такими интерактивными элементами, как <details> и <summary>, предназначенными для решения классических интерфейсных задач. С 2014-го года <dialog> был доступен только в браузерах Google Chrome и Opera, а в Firefox и Safari полноценная поддержка появилась лишь в марте 2022-го года. По этой причине <dialog> довольно редко использовался в реальных проектах. Однако с учётом почти двухлетней поддержки основными браузерами, стандарт стал достаточно устойчивым, чтобы с уверенностью заменить самописные <div class="modal" tabindex="-1" role="dialog" aria-modal="true"> на нативную реализацию.

Давайте познакомимся с возможностями <dialog> поближе.

Основные особенности использования

HTML-тег <dialog> создаёт скрытое по умолчанию диалоговое окно на странице, которое может функционировать в двух режимах: в качестве всплывающего поп-апа или в роли модального окна.

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

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

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

Методы для открытия диалогового окна

Всплывающий поп-ап:

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

В обоих случаях при открытии окна тегу <dialog> проставляется булевый атрибут open в значении true. Значение атрибута можно установить в true напрямую, однако в этом случае диалоговое окно откроется как поп-ап — работать с ним как с модалкой просто не получится. Поэтому для рендеринга модальных окон необходимо использовать только соответствующий метод. Для создания изначально открытого поп-апа можно обойтись и без JS:

Попробовать в деле:

Открытие поп-апа с помощью метода .show()
Открытие модалки с помощью метода .showModal()
Изменение атрибута open напрямую

Способы закрытия диалогового окна

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

Через вызов метода .close():

Через инициацию события submit в контексте формы с атрибутом method="dialog":

Нажатием клавиши Esc. Закрытие с помощью клавиши Esc работает только для модальных окон. При закрытии таким способом сначала запускается событие cancel, и только потом close — так, например, удобно предупреждать пользователя о том, что изменённые данные в форме внутри модалки не сохранятся.

Попробовать в деле:

Закрытие диалогового окна через метод close
Закрытие диалогового окна через submit формы
Закрытие модалки через нажатие Esc
Предотвращение закрытия модалки по Esc через прослушивание события cancel

Возвращаемое значение при закрытии

При закрытии диалогового окна через форму с атрибутом method="dialog" можно получить и обработать значение, указывающее на кнопку, которая была нажата перед закрытием. Это удобно, если после нажатия разных закрывающих кнопок требуется выполнить разные действия на странице. Для этого можно обратиться к свойству элемента диалогового окна returnValue, которое будет содержать значение атрибута value той кнопки, на которую нажал пользователь, чтобы закрыть окно.

Попробовать в деле.

Подробнее про механику работы

Рассмотрим более подробно механику работы диалогового окна и детали браузерной реализации.

Механика работы всплывающего поп-апа

Если элемент <dialog> был открыт как всплывающий поп-ап через метод .show() или напрямую через указание атрибута open, движок браузера автоматически разместит поп-ап в виде абсолютно спозиционированного блочного элемента в том месте, где он был указан в DOM. Для этого элемента будут применены базовые CSS-стили, включая отступы и границы, а первый фокусируемый элемент внутри окна получит фокус автоматически через глобальный атрибут autofocus. При этом сохранится возможность взаимодействия с остальной частью страницы.

Механика работы модального окна

Модальное окно устроено и работает несколько сложнее, чем поп-ап.

Перекрытие документа

При открытии модального окна с использованием метода .showModal() элемент <dialog> рендерится в специальном слое HTML-документа. Этот слой охватывает всю ширину и высоту видимой области страницы, располагаясь поверх всего документа. Такой слой называется верхним слоем документа (top layer), и является внутренней концепцией браузера — напрямую управлять им невозможно. В определённых браузерах, например, в Google Chrome, каждое модальное окно рендерится в отдельном DOM-узле верхнего слоя, которые можно увидеть в инспекторе элементов:

!

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

Подробнее про stacking context можно почитать тут. Подробнее про то, какие элементы рендерятся в top layerтут

Блокировка документа

Когда элемент модального окна рендерится в верхнем слое, под ним создаётся псевдо-элемент подложки ::backdrop, которому устанавливаются размеры текущей видимой области документа. Эта подложка блокирует действия на остальной странице, даже если для неё установлено CSS-свойство pointer-events: none.

Дополнительная блокировка пользовательских действий обеспечивается путём автоматической установки глобального атрибута inert для всех элементов, за исключением модального окна. Атрибут inert предотвращает срабатывание событий клика и фокусировки в пределах элементов, для которых он установлен, а также прячет их от экранных дикторов (скринридеров) и других вспомогательных технологий, обеспечивающих доступность (accessibility).

Подробнее про атрибут inert.

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

Первый фокусируемый элемент внутри модалки автоматически попадёт в фокус в момент её открытия. Для изменения элемента, который будет иметь изначальный фокус, можно воспользоваться атрибутами autofocus или tabindex. Установка tabindex для элемента диалогового окна невозможна, поскольку он, в любом случае, является единственным элементом страницы, для которого не применяется логика атрибута inert.

При закрытии диалогового окна фокус возвращается на тот элемент, который вызвал его открытие.

Решение проблем взаимодействия с модальными окнами

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

Блокировка скролла

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

Такое css-правило придётся динамически добавлять и убирать каждый раз при открытии и закрытии модального окна. Этого можно достичь путём манипуляции классом, содержащим данное CSS-правило:

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

Попробовать в деле.

Закрытие диалога по клику на свободной области

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

Способ, основанный на особенностях работы псевдоэлемента подложки ::backdrop

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

Не забудем сбросить стандартные браузерные стили отступов и границ у элемента <dialog>, чтобы предотвратить закрытие модального окна при случайном клике по ним:

Теперь стилизацию общих для окна границ и отступов мы применяем только к внутренней обёртке.

Осталось написать функцию, которая будет закрывать модальное окно только при клике на подложку, а не на внутренний элемент обёртки:

Попробовать в деле.

Способ, основанный на определении размеров диалогового окна

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

Попробовать в деле.

Стилизация диалогового окна

В отличие от многих нативных HTML-элементов, элемент <dialog> предоставляет значительную гибкость в плане стилизации. Вот несколько готовых рецептов для стилизации диалоговых окон:

Стилизация фона подложки через селектор ::backdrop:
Анимированное открытие и закрытие окна
Модальное окно в виде сайдбара

Доступность

Хотя долгое время элемент <dialog> имел некоторые проблемы с соответствием стандартам доступности (accessibility), на данный момент основные вспомогательные технологии, такие как экранные дикторы (VoiceOver, TalkBack, NVDA), хорошо работают с диалоговыми окнами.

При открытии элемента <dialog>, фокус экранного диктора переводится на диалоговое окно, а в случае с модалкой — остаётся в её пределах до тех пор, пока она открыта.

Нативный элемент <dialog> по умолчанию распознаётся вспомогательными технологиями как элемент с ARIA-атрибутом role="dialog". Элемент <dialog>, открытый как модальное окно, будет восприниматься как элемент с ARIA-атрибутом aria-modal="true".

Вот несколько рекомендаций, как улучшить доступность элемента <dialog>:

aria-labelledby

Всегда используйте заголовок внутри диалоговых окон и указывайте атрибут aria-labelledby для элемента <dialog>, со значением идентификатора заголовка:

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

aria-describedby

Используйте атрибут aria-describedby для связи с содержимым диалогового окна. Некоторые скринридеры не смогут прочитать содержимое элемента <dialog> без этого атрибута. Заголовки и любые интерактивные элементы для управления состоянием диалогового окна должны быть вынесены отдельно за пределы элемента с содержимым:

aria-label

Всегда добавляйте кнопку для закрытия диалоговых окон, особенно внутри модалок. Для лучшей доступности необходимо использовать именно элемент <button>. Для кнопок, которые не содержат очевидный для пользователя текст, необходимо указать этот текст в ARIA-атрибуте aria-label:

Браузерная поддержка

Нативный элемент диалогового окна представляет собой удобный и мощный инструмент для решения стандартных интерфейсных задач. К сожалению, его поддержка в основных браузерах была добавлена сравнительно недавно, и в более экзотических или устаревших браузерах поддержки всё ещё может не быть. При отсутствии поддержки нативного элемента <dialog>, можно воспользоваться полифилом, разработанным командой Google Chrome.

Скрипты и стили полифила можно подключить локально, использовать CDN или установить его как npm-зависимость: npm install dialog-polyfill.

Если полифил подключён не через импорт npm-пакета, не забудьте отдельно подключить стили.

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

Подключать полифил рекомендуется через динамический импорт и только для тех клиентов, которые не поддерживают элемент <dialog>:

Заключение

Нативный HTML-элемент <dialog> — это относительно простой и очень мощный инструмент для реализации модальных окон и поп-апов. Он отлично поддерживается современными браузерами и может успешно использоваться как в проектах на чистом JS, так и в контексте любого фронтенд-фреймворка.

В данной статье мы охватили следующие темы:

— Проблемы, которые призван решить элемент <dialog>;
— Взаимодействие с API элемента <dialog>;
— Механика работы с диалоговыми окнами на уровне браузера;
— Возможные проблемы при работе с модальными окнами и их решения;
— Улучшение доступности элемента <dialog> для вспомогательных устройств, таких как скринридеры;
— Расширение браузерной поддержки элемента <dialog>.

Напоследок приглашаю рассмотреть реализацию компонента модального окна на чистом JS, в которой учтены основные аспекты, описанные в статье.

Это всё, что я хотел бы рассказать про особенности работы с HTML-элементом <dialog>. Надеюсь, что данная статья вдохновит вас на эксперименты, жду ваших вопросов в комментариях!

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

Добавить комментарий

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