Реализация модальных окон (Popup) под ключ с focus trap и анимацией

Реализация всплывающих окон (Modal/Popup) на сайте

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Реализация модальных окон (Popup) под ключ с focus trap и анимацией
Простой
~1 день

Наши компетенции:

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    982
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    995

Реализация всплывающих окон (Modal/Popup) на сайте

При разработке корпоративного портала на React мы столкнулись с проблемой: после открытия модального окна подтверждения удаления записи скролл на фоне продолжал работать, а фокус перемещался на кнопку внизу страницы. Это приводило к ошибкам пользователей — они случайно удаляли данные, не видя подтверждения. Конверсия на целевом действии упала на 12% за первую неделю. Пришлось переписать всю систему с нуля, используя нативный <dialog> и кастомный focus trap. Сейчас мы реализуем модальные окна под ключ, решая эти проблемы на уровне архитектуры. Опыт более 50 проектов показывает: правильная реализация модалок экономит до 40% времени на отладку UI и снижает количество инцидентов на 25%. Средняя экономия на поддержке составляет около 30% бюджета.

Почему нативный <dialog> лучше самодельных решений?

Многие разработчики создают модалки на div с нуля, забывая про accessibility и управление фокусом. Нативный <dialog> с showModal() автоматически блокирует фон, захватывает фокус и поддерживает закрытие по Escape. Наш опыт показывает, что такой подход сокращает время разработки в два раза по сравнению с кастомным решением. Кроме того, нативный dialog обеспечивает корректную работу с экранными читалками и не требует дополнительных библиотек.

Параметр Нативный <dialog> Кастомный <div>
Focus trap Встроен в showModal() Нужно реализовывать вручную
Блокировка скролла Автоматически через ::backdrop Требуется установка overflow: hidden на body
Доступность Поддержка ARIA-атрибутов, роль dialog Нужно добавлять роли и атрибуты
Вложенные модалки Стек управляется нативно Нужен собственный стек менеджер
Анимация Через CSS animation на элементах То же

Как реализовать focus trap без нативного dialog?

Если проект требует кастомного решения (например, из-за особенностей дизайн-системы), focus trap реализуется вручную. Собираем все фокусируемые элементы внутри модалки и перехватываем Tab/Shift+Tab. При открытии сохраняем предыдущий активный элемент, чтобы вернуть фокус при закрытии. В React это удобно обернуть в хук useFocusTrap.

Реализация модального окна в React: пошагово

  1. Создайте компонент Modal, который принимает isOpen, onClose, title, children и другие пропсы.
  2. Используйте createPortal для рендеринга модалки в body.
  3. Внутри useEffect открывайте и закрывайте native <dialog> через showModal()/close().
  4. Управляйте overflow: hidden на body при открытии.
  5. Реализуйте focus trap: сохраняйте предыдущий активный элемент и возвращайте фокус при закрытии.
  6. Добавьте обработку клика по backdrop для закрытия.
  7. Примените CSS-анимации через ключевые кадры.

Пример готового компонента:

import { useEffect, useRef, ReactNode } from 'react' import { createPortal } from 'react-dom' interface ModalProps { isOpen: boolean onClose: () => void title?: string children: ReactNode size?: 'sm' | 'md' | 'lg' | 'xl' | 'full' closeOnBackdrop?: boolean } export function Modal({ isOpen, onClose, title, children, size = 'md', closeOnBackdrop = true, }: ModalProps) { const dialogRef = useRef<HTMLDialogElement>(null) const previousFocusRef = useRef<HTMLElement | null>(null) useEffect(() => { const dialog = dialogRef.current if (!dialog) return if (isOpen) { previousFocusRef.current = document.activeElement as HTMLElement dialog.showModal() document.body.style.overflow = 'hidden' } else { dialog.close() document.body.style.overflow = '' previousFocusRef.current?.focus() } }, [isOpen]) useEffect(() => { const dialog = dialogRef.current const handleClose = () => onClose() dialog?.addEventListener('close', handleClose) return () => dialog?.removeEventListener('close', handleClose) }, [onClose]) function handleBackdropClick(e: React.MouseEvent<HTMLDialogElement>) { if (!closeOnBackdrop) return const rect = dialogRef.current!.getBoundingClientRect() if ( e.clientX < rect.left || e.clientX > rect.right || e.clientY < rect.top || e.clientY > rect.bottom ) { onClose() } } return createPortal( <dialog ref={dialogRef} className={`modal modal--${size}`} onClick={handleBackdropClick} aria-labelledby={title ? 'modal-title' : undefined} > <div className="modal__content" onClick={e => e.stopPropagation()}> {title && ( <div className="modal__header"> <h2 id="modal-title" className="modal__title">{title}</h2> <button className="modal__close" onClick={onClose} aria-label="Закрыть"> <svg viewBox="0 0 24 24" width="20" height="20"> <path d="M6 6l12 12M18 6l-12 12" stroke="currentColor" strokeWidth="2"/> </svg> </button> </div> )} <div className="modal__body">{children}</div> </div> </dialog>, document.body ) } 

Типичные ошибки при реализации модальных окон

  1. Игнорирование блокировки скролла — фон прокручивается под открытой модалкой. Решение: устанавливать overflow: hidden на <body> при открытии и снимать при закрытии. При использовании нативного <dialog> это автоматически не происходит, поэтому добавляйте вручную.
  2. Потеря фокуса — после закрытия модалки фокус не возвращается на элемент, вызвавший её. В React-компоненте выше сохраняется previousFocusRef для коррекции.
  3. Некорректная работа на мобильных — стандартный <dialog> не выглядит как bottomsheet. Используйте медиа-запросы, как показано выше, чтобы на мобильных модалка выезжала снизу.

Адаптация под мобильные устройства: bottomsheet

На мобильных устройствах принято использовать bottomsheet — модалка выезжает снизу. В CSS это реализуется через медиа-запрос: на экранах шириной до 768px меняем анимацию на translateY и скругляем только верхние углы. Также стоит учитывать отступ для адресной строки браузера и использовать dvh для высоты.

Что входит в реализацию модальных окон?

  • Документация по API компонента
  • Исходный код на TypeScript/React/Vue
  • Тестирование на desktop и mobile (iOS, Android)
  • Интеграция с аналитикой (триггеры показа)
  • Поддержка после деплоя (1 месяц)

Сроки

Этап Время
Базовая реализация (нативный <dialog>) 3–4 часа
React-компонент с порталом и focus trap 1 день
Система со стеком модалок и bottomsheet 1.5–2 дня
Полная адаптация под WCAG +0.5 дня

У нас более 7 лет опыта в разработке интерфейсов, более 50 проектов с модальными окнами. Мы гарантируем кросс-браузерность и accessibility. Закажите консультацию по реализации модальных окон в вашем проекте — мы подберем оптимальное решение. Свяжитесь с нами, чтобы обсудить детали.