Создание доступных поповеров с помощью современных CSS и JS

Поповеры представляют контекстную информацию, не нарушая рабочий процесс пользователя, но их доступная реализация остается вызовом для многих разработчиков. Независимо от того, модернизируете ли вы устаревший код или создаете библиотеку компонентов, понимание различий между поповерами, подсказками и модальными окнами имеет решающее значение для создания правильного пользовательского опыта.
В этой статье рассматривается, как создавать доступные поповеры, используя современные CSS и JavaScript, от динамического позиционирования до навигации с клавиатуры, а также изучаются нативные браузерные API, которые снижают сложность.
Ключевые выводы
- Поповеры отображают богатый интерактивный контент, который сохраняется до закрытия, в отличие от подсказок, которые показывают краткие подсказки при наведении
- Нативный Popover API устраняет сложность JavaScript, предоставляя встроенные функции доступности
- Правильное управление фокусом и ARIA-атрибуты необходимы для навигации с клавиатуры
- Динамическое позиционирование обеспечивает видимость поповеров в границах области просмотра
Понимание различий между поповерами, подсказками и модальными окнами
Подсказки предоставляют краткие подсказки при наведении, обычно содержащие одну строку текста. Они исчезают, когда пользователи убирают курсор, и не могут содержать интерактивные элементы.
Поповеры отображают более богатый контент — заголовки, абзацы, кнопки или формы. Они остаются видимыми до явного закрытия, позволяя пользователям взаимодействовать как с содержимым поповера, так и со страницей под ним.
Модальные окна создают сфокусированный опыт, делая фон неактивным. Пользователи должны завершить взаимодействие с модальным окном, прежде чем вернуться к основному контенту.
Основные требования к реализации
Динамическое позиционирование в области просмотра
Современные поповеры должны адаптироваться к доступному пространству экрана. Когда поповер выходит за границы области просмотра, он должен автоматически перепозиционироваться:
const positionPopover = (trigger, popover) => {
const triggerRect = trigger.getBoundingClientRect()
const popoverRect = popover.getBoundingClientRect()
let top = triggerRect.bottom + 8
let left = triggerRect.left
// Flip to top if insufficient space below
if (top + popoverRect.height > window.innerHeight) {
top = triggerRect.top - popoverRect.height - 8
popover.classList.add('popover--top')
}
// Adjust horizontal position
if (left + popoverRect.width > window.innerWidth) {
left = window.innerWidth - popoverRect.width - 16
}
popover.style.top = `${top}px`
popover.style.left = `${left}px`
}
Выравнивание стрелки
CSS обрабатывает визуальную стрелку, которая указывает на элемент-триггер:
.popover::after {
content: "";
position: absolute;
width: 12px;
height: 12px;
background: inherit;
border: inherit;
transform: rotate(45deg);
top: -7px;
left: 20px;
border-bottom: 0;
border-right: 0;
}
.popover--top::after {
top: auto;
bottom: -7px;
transform: rotate(225deg);
}
Механизмы закрытия
Доступные поповеры требуют множественных способов закрытия:
// Click outside
document.addEventListener('click', (e) => {
if (!popover.contains(e.target) && !trigger.contains(e.target)) {
closePopover()
}
})
// ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isPopoverOpen) {
closePopover()
trigger.focus() // Return focus to trigger
}
})
Discover how at OpenReplay.com.
Нативный Popover API
Popover API устраняет большую часть сложности JavaScript:
<button popovertarget="my-popover">Open Info</button>
<div id="my-popover" popover>
<h3>Additional Information</h3>
<p>This popover requires no JavaScript for basic functionality.</p>
<button popovertarget="my-popover">Close</button>
</div>
Этот нативный подход автоматически обрабатывает позиционирование, закрытие и управление фокусом. Для улучшения доступности комбинируйте его с элементом <dialog>
:
<dialog id="enhanced-popover" popover>
<h2>Accessible Popover</h2>
<p>Combining dialog with popover provides semantic meaning.</p>
<button popovertarget="enhanced-popover">Close</button>
</dialog>
Сравнение библиотечных и нативных решений
Традиционные библиотеки, такие как Popper.js, предлагают обширные алгоритмы позиционирования, но добавляют 15-30 КБ к вашему бандлу. Нативный Popover API предоставляет:
- Нулевой JavaScript для базовой функциональности
- Встроенные функции доступности
- Автоматическое управление фокусом
- Оптимизированное браузером позиционирование
Для сложных требований позиционирования библиотеки остаются ценными. Для стандартных случаев использования нативные решения значительно снижают сложность.
Основные соображения доступности
ARIA-атрибуты
При создании пользовательских поповеров без нативного API:
<button
aria-expanded="false"
aria-controls="custom-popover"
aria-haspopup="dialog">
Open Popover
</button>
<div
id="custom-popover"
role="dialog"
aria-labelledby="popover-title"
aria-modal="false">
<h2 id="popover-title">Popover Title</h2>
<!-- Content -->
</div>
Управление фокусом
Правильный порядок фокуса обеспечивает эффективную навигацию для пользователей клавиатуры:
const focusableElements = popover.querySelectorAll(
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
)
// Trap focus within popover
popover.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault()
lastElement.focus()
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault()
firstElement.focus()
}
}
})
Предотвращение прокрутки фона
CSS сам по себе может предотвратить прокрутку фона при использовании нативного API:
body:has(dialog[popover]:popover-open) {
overflow: hidden;
}
Заключение
Создание доступных поповеров требует баланса между потребностями пользователей и технической реализацией. Нативный Popover API упрощает разработку, сохраняя стандарты доступности, хотя пользовательские решения остаются необходимыми для сложных взаимодействий.
Сосредоточьтесь на навигации с клавиатуры, правильной реализации ARIA и четких паттернах закрытия. Независимо от того, используете ли вы нативные API или создаете пользовательские компоненты, доступность должна определять ваши решения по реализации — обеспечивая работу ваших поповеров для всех пользователей, независимо от того, как они взаимодействуют с вашим интерфейсом.
Часто задаваемые вопросы
Используйте нативный Popover API для стандартных реализаций, поскольку он предоставляет встроенную доступность и не требует JavaScript. Выбирайте библиотеки, такие как Popper.js, только когда вам нужна сложная логика позиционирования или необходимо поддерживать старые браузеры, которые не поддерживают нативный API.
Подсказки показывают краткий текст при наведении и исчезают автоматически, требуя только простых ARIA-меток. Поповеры содержат интерактивные элементы, нуждаются в управлении фокусом, множественных методах закрытия и правильных ARIA-атрибутах, включая role dialog и aria-modal, чтобы обеспечить корректное объявление их программами чтения с экрана.
Popover API поддерживается в Chrome 114+, Edge 114+ и Safari 17+. Поддержка Firefox находится в разработке. Всегда проверяйте текущую совместимость браузеров и предоставляйте резервные варианты для неподдерживаемых браузеров, используя обнаружение функций перед внедрением в продакшн.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.