Как использовать html-элемент ?
Привет, Хабр! Меня зовут Александр Григоренко, я фронтенд-разработчик. В основном, занимаюсь разработкой приложений на React, но также постоянно экспериментирую с различными технологиями.
В своей работе я часто создаю собственные или использую уже готовые UI-компоненты. Проблема с такими компонентами заключается в том, что они часто ограничены определённым фреймворком, и их реализация требует написания сложной нестандартизированной логики. В течение долгого времени для базовых UI-компонентов, таких как диалоговые окна, использовались самописные решения, а в тяжёлых случаях и встроенные в JavaScript методы alert()
, prompt()
и confirm()
.
Отличная новость в том, что такой компонент можно реализовать с использованием нативного HTML-элемента , который встроен в стандарт HTML5 и работает одинаково во всех современных браузерах.
В статусе рабочего черновика W3C тег Давайте познакомимся с возможностями HTML-тег Всплывающие поп-апы обычно используются для показа ненавязчивых уведомлений, таких как сообщения об использовании на сайте файлов cookie, автоматически исчезающих toast-сообщений, тултипов и даже элементов, имитирующих контекстное меню, вызываемое нажатием правой клавиши мыши. Модальные окна применяются, когда необходимо сосредоточить внимание пользователя на конкретной задаче. Это могут быть уведомления и предупреждения, требующие от пользователя подтверждения действий на странице, сложные интерактивные формы, и, например, лайтбоксы для просмотра изображений или видеороликов. Всплывающий поп-ап не мешает взаимодействию со страницей, в отличие от модального окна, которое открывается поверх всего документа, затемняет фон вокруг себя и блокирует любые действия с остальным контентом. Эта логика работает без необходимости в дополнительных стилях и скриптах; единственное отличие заключается в том, какой метод вызывается для открытия диалога. — всплывающий поп-ап: — модальное окно: В обоих случаях при открытии окна тегу Попробовать в деле: Закрываются диалоговые окна одинаково, независимо от того, каким образом они были открыты. Вот несколько способов закрыть всплывающее или модальное окно: — через вызов метода — через инициацию события — нажатием клавиши Esc: Закрытие с помощью клавиши Esc работает только для модальных окон. При закрытии таким способом сначала запускается событие Попробовать в деле: При закрытии диалогового окна через форму с атрибутом Попробовать в деле: https://codepen.io/alexgriss/pen/ZEwmBKx Рассмотрим более подробно механику работы диалогового окна и детали браузерной реализации. Если элемент Модальное окно устроено и работает несколько сложнее, чем поп-ап. При открытии модального окна с использованием метода top layer Понятие слоёв относится к концепции контекста наложения (stacking context), описывающей, как элементы располагаются относительно друг друга вдоль оси Z по отношению к пользователю, находящемуся перед экраном. Например, при задании значения CSS-свойства Подробнее про stacking context можно почитать тут: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context Подробнее про то, какие элементы рендерятся в top layer — тут: https://developer.mozilla.org/en-US/docs/Glossary/Top_layer Когда элемент модального окна рендерится в верхнем слое, под ним создаётся псевдо-элемент подложки Дополнительная блокировка пользовательских действий обеспечивается путём установки глобального атрибута Подробнее про атрибут Первый фокусируемый элемент внутри модалки автоматически попадёт в фокус в момент её открытия. Для изменения элемента, который будет иметь изначальный фокус, можно воспользоваться атрибутами При закрытии диалогового окна фокус возвращается на тот элемент, который вызвал его открытие. К сожалению, нативная реализация элемента Хотя в нативной реализации модального окна и создаётся псевдоэлемент Такое css-правило придётся динамически добавлять и убирать каждый раз при открытии и закрытии модального окна. Этого можно достичь путём манипуляции классом, содержащим данное CSS-правило: Также можно воспользоваться селектором Попробовать в деле: https://codepen.io/alexgriss/pen/XWOyVKj Это стандартный UX-сценарий для модального окна и он может быть реализован несколькими способами. Предлагаю ознакомиться с двумя способами решения этой проблемы: Способ, основанный на особенностях работы псевдоэлемента подложки Клик по псевдоэлементу подложки рассматривается как клик по самому элементу диалога. Следовательно, если весь контент модального окна обернуть в дополнительный Не забудем сбросить стандартные браузерные стили отступов и границ у элемента Теперь стилизацию общих для окна границ и отступов мы применяем только к внутренней обёртке. Осталось написать функцию, которая будет закрывать модальное окно только при клике на подложку, а не на внутренний элемент обёртки: Попробовать в деле: https://codepen.io/alexgriss/pen/mdvQXpJ Способ, основанный на определении размеров диалогового окна В отличие от первого способа, который требовал обёртывания внутреннего содержимого модального окна в дополнительный элемент, этот способ не требует использования дополнительной обёртки. Всё, что необходимо, — это проверить, выходят ли координаты курсора за пределы области элемента окна при клике: Попробовать в деле: https://codepen.io/alexgriss/pen/NWoePVP В отличие от многих нативных HTML-элементов, элемент Стилизация фона подложки через селектор Анимированное открытие и закрытие окна: https://codepen.io/alexgriss/pen/QWYJQJO Модальное окно в виде сайдбара: https://codepen.io/alexgriss/pen/GRzwxgr Хотя долгое время элемент При открытии элемента Нативный элемент Вот несколько рекомендаций, как улучшить доступность элемента Всегда используйте заголовок внутри диалоговых окон и указывайте атрибут В таком случае экранные дикторы будут зачитывать содержимое этого заголовка при открытии диалогового окна. Используйте атрибут Всегда добавляйте кнопку для закрытия диалоговых окон, особенно внутри модалок. Для лучшей доступности необходимо использовать именно элемент Нативный элемент диалогового окна представляет собой удобный и мощный инструмент для решения стандартных интерфейсных задач. К сожалению, его поддержка в основных браузерах была добавлена сравнительно недавно, и в более экзотических или устаревших браузерах поддержки всё ещё может не быть. При отсутствии поддержки нативного элемента Скрипты и стили полифила можно подключить локально, использовать CDN или установить его как npm-зависимость: Если полифил подключён не через импорт npm-пакета, не забудьте отдельно подключить стили: https://github.com/GoogleChrome/dialog-polyfill/blob/master/dist/dialog-polyfill.css. Если требуется стилизовать псевдоэлемент подложки модального окна Подключать полифил рекомендуется через динамический импорт и только для тех клиентов, которые не поддерживают элемент Нативный HTML-элемент В данной статье мы охватили следующие темы: Проблемы, которые призван решить элемент Взаимодействие с API элемента Механика работы с диалоговыми окнами на уровне браузера; Возможные проблемы при работе с модальными окнами и их решения; Улучшение доступности элемента Расширение браузерной поддержки элемента Напоследок приглашаю рассмотреть реализацию компонента модального окна на чистом JS, в которой учтены основные аспекты, описанные в статье. Попробовать в деле: https://codepen.io/alexgriss/pen/abXPOPP Это всё, что я хотел бы рассказать про особенности работы с HTML-элементом Приглашаю вас подписаться на мой телеграм-канал: https://t.me/alexgriss, в котором я пишу о фронтенд-разработке, публикую полезные материалы, делюсь своим профессиональным мнением и рассматриваю темы, важные для карьеры разработчика. появился в мае 2013-го года вместе с такими интерактивными элементами, как
и
, предназначенными для решения классических интерфейсных задач. С 2014-го года был доступен только в браузерах Google Chrome и Opera, а в Firefox и Safari полноценная поддержка появилась лишь в марте 2022-го года. По этой причине
довольно редко использовался в реальных проектах. Однако с учётом почти двухлетней поддержки основными браузерами, стандарт стал достаточно устойчивым, чтобы с уверенностью заменить самописные
поближе.
Основные особенности использования
создаёт скрытое по умолчанию диалоговое окно на странице, которое может функционировать в двух режимах: в качестве всплывающего поп-апа или в роли модального окна.
Методы для открытия диалогового окна
const popUpElement = document.getElementById("pop-up");
popUpElement.show();
сonst modalElement = document.getElementById("modal");
modalElement.showModal();
проставляется булевый атрибут
open
в значении true
. Значение атрибута можно установить в true
напрямую, однако в этом случае диалоговое окно откроется как поп-ап — работать с ним как с модалкой просто не получится. Поэтому для рендеринга модальных окон необходимо использовать только соответствующий метод. Для создания изначально открытого поп-апа можно обойтись и без JS:
Способы закрытия диалогового окна
.close()
: сonst dialogElement = document.getElementById("dialog");
dialogElement.close();
submit
в контексте формы с атрибутом method="dialog"
:
cancel
, и только потом close
— так, например, удобно предупреждать пользователя о том, что изменённые данные в форме внутри модалки не сохранятся.Возвращаемое значение при закрытии
method="dialog"
можно получить и обработать значение, указывающее на кнопку, которая была нажата перед закрытием. Это удобно, если после нажатия разных закрывающих кнопок требуется выполнить разные действия на странице. Для этого можно обратиться к свойству элемента диалогового окна returnValue
, которое будет содержать значение атрибута value
той кнопки, на которую нажал пользователь, чтобы закрыть окно.Подробнее про механику работы
Механика работы всплывающего поп-апа
был открыт как всплывающий поп-ап через метод
.show()
или напрямую через указание атрибута open
, движок браузера автоматически разместит поп-ап в виде абсолютно спозиционированного блочного элемента в том месте, где он был указан в DOM. Для этого элемента будут применены базовые CSS-стили, включая отступы и границы, а первый фокусируемый элемент внутри окна получит фокус автоматически через глобальный атрибут autofocus
. При этом сохранится возможность взаимодействия с остальной частью страницы.Механика работы модального окна
Перекрытие документа
.showModal()
элемент рендерится в специальном слое HTML-документа. Этот слой охватывает всю ширину и высоту видимой области страницы, располагаясь поверх всего документа. Такой слой называется верхним слоем документа (top layer), и является внутренней концепцией браузера — напрямую управлять им невозможно. В определённых браузерах, например, в Google Chrome, каждое модальное окно рендерится в отдельном DOM-узле верхнего слоя, которые можно увидеть в инспекторе элементов:
z-index
для элемента, мы создаём замкнутый на этом элементе контекст наложения. Так позиция элемента будет рассчитываться относительно позиций его соседей, а все значения z-index
дочерних элементов будут учитываться только в рамках контекста наложения родителя. Такую иерархию контекстов наложения можно представить в виде слоистой структуры, а открытое модальное окно всегда будет находиться наверху этой иерархии, так как оно рендерится в верхнем слое, и для него не нужно устанавливать CSS-правило z-index
.Блокировка документа
::backdrop
, которому устанавливаются размеры текущей видимой области документа. Эта подложка блокирует действия на остальной странице, даже если для неё установлено CSS-свойство pointer-events: none
.inert
для всех элементов, за исключением модального окна. Атрибут inert
предотвращает срабатывание событий клика и фокусировки в пределах элементов, для которых он установлен, а также прячет их от экранных дикторов (скринридеров) и других вспомогательных технологий, обеспечивающих доступность (accessibility).inert
: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/inertПоведение фокуса
autofocus
или tabindex
. Установка tabindex
для элемента диалогового окна невозможна, поскольку он, в любом случае, является единственным элементом страницы, для которого не применяется логика атрибута inert
.Решение проблем взаимодействия с модальными окнами
не охватывает все аспекты взаимодействия с модальными окнами. Далее, я предлагаю рассмотреть решения основных UX-проблем, которые могут возникнуть при использовании модальных окон.
Блокировка скролла
::backdrop
, который находится поверх страницы и блокирует взаимодействие с контентом — скролл страницы всё ещё доступен. Это может отвлекать пользователя, поэтому при открытии модального окна рекомендуется обрезать содержимое body
: body {
overflow: hidden;
}
// При открытии модалки
document.body.classList.add("scroll-lock");
// При закрытии модалки
document.body.classList.remove("scroll-lock");
:has
, если статус поддержки этого селектора соответствует требованиям проекта: body:has(dialog[open]) {
overflow: hidden;
}
Закрытие диалога по клику на свободной области
::backdrop
, чтобы предотвратить закрытие модального окна при случайном клике по ним:
dialog {
padding: 0;
border: none;
}
const handleModalClick = ({ currentTarget, target }) => {
const isClickedOnBackdrop = target === currentTarget;
if (isClickedOnBackdrop) {
currentTarget.close();
}
}
modalElement.addEventListener("click", handleModalClick);
const handleModalClick = (event) => {
const modalRect = modalElement.getBoundingClientRect();
if (
event.clientX < modalRect.left ||
event.clientX > modalRect.right ||
event.clientY < modalRect.top ||
event.clientY > modalRect.bottom
) {
modalElement.close();
}
};
modalElement.addEventListener("click", handleModalClick);
Стилизация диалогового окна
предоставляет значительную гибкость в плане стилизации. Вот несколько готовых рецептов для стилизации диалоговых окон:
::backdrop
: https://codepen.io/alexgriss/pen/ExrOQEOДоступность
имел некоторые проблемы с соответствием стандартам доступности (accessibility), на данный момент основные вспомогательные технологии, такие как экранные дикторы (VoiceOver, TalkBack, NVDA), хорошо работают с диалоговыми окнами.
, фокус экранного диктора переводится на диалоговое окно, а в случае с модалкой — остаётся в её пределах до тех пор, пока она открыта.
по умолчанию распознаётся вспомогательными технологиями как элемент с ARIA-атрибутом
role="dialog"
. Элемент , открытый как модальное окно, будет восприниматься как элемент с ARIA-атрибутом
aria-modal="true"
.:
aria-labelledby
aria-labelledby
для элемента , со значением идентификатора заголовка:
aria-describedby
aria-describedby
для связи с содержимым диалогового окна. Некоторые скринридеры не смогут прочитать содержимое элемента без этого атрибута. Заголовки и любые интерактивные элементы для управления состоянием диалогового окна должны быть вынесены отдельно за пределы элемента с содержимым:
aria-label
. Для кнопок, которые не содержат очевидный для пользователя текст, необходимо указать этот текст в ARIA-атрибуте
aria-label
:
Браузерная поддержка
, можно воспользоваться полифилом, разработанным командой Google Chrome.
npm install dialog-polyfill
.::backdrop
, убедитесь, что вы также применяете стили к соответствующему элементу с классом .backdrop
для обеспечения совместимости с более старыми браузерами: dialog::backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
dialog + .backdrop {
background-color: rgba(0, 0, 0, 0.5);
}
:
const isBrowserNotSupportDialog = window.HTMLDialogElement === undefined;
if (isBrowserNotSupportDialog) {
const dialogElement = document.getElementById("modal");
const { default: polyfill } = await import("dialog-polyfill");
polyfill.registerDialog(dialogElement);
}
В заключение
— это относительно простой и очень мощный инструмент для реализации модальных окон и поп-апов. Он отлично поддерживается современными браузерами и может успешно использоваться как в проектах на чистом JS, так и в контексте любого фронтенд-фреймворка.
;
;
для вспомогательных устройств, таких как скринридеры;
.
. Надеюсь, что данная статья вдохновит вас на эксперименты, жду ваших вопросов в комментариях!