Реалізація модальних вікон (Popup) під ключ із focus trap та анімацією

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

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

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

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

Часті запитання

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1467
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1317
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1014
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1276
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1019
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1019

Реалізація спливаючих вікон (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. Замовте консультацію з реалізації модальних вікон у вашому проєкті — ми підберемо оптимальне рішення. Зв'яжіться з нами, щоб обговорити деталі.