Порталы для модальных окон и подсказок
Используйте ReactDOM.createPortal для рендеринга дочерних элементов в другом узле DOM, чтобы модальные окна и подсказки могли выходить за пределы контейнеров overflow:hidden
«Порталы для модальных окон и подсказок» — бесплатный урок Frontend Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Frontend Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Frontend Academy содержит 4 уроков всего.
Проблема выхода из контейнера
Модальные окна и всплывающие подсказки нужно отрисовывать в верхней части DOM, чтобы избежать ограничений overflow:hidden, наложения z-index и контекстов, созданных transform у родительского элемента. Решение — порталы.
API createPortal
createPortal(children, container) отрисовывает дочерние элементы в контейнере (любом узле DOM), сохраняя их логически дочерними элементами компонента React для событий и контекста.
import { createPortal } from 'react-dom';
function Modal({ children, onClose }) {
return createPortal(
<div className="modal-backdrop" onClick={onClose}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Контейнеры порталов
Распространённые цели: document.body, отдельный элемент #modal-root в index.html или контейнер, сохранённый в ref и созданный по запросу.
<!-- index.html -->
<div id="root"></div>
<div id="modal-root"></div>
// component:
const modalRoot = document.getElementById('modal-root');
return createPortal(<div>...</div>, modalRoot);Почему не отрисовывать всё непосредственно
Если у родительского элемента задано overflow: hidden, дочернее модальное окно будет обрезано. Если у родителя задано transform: scale, фиксированное позиционирование модального окна станет относительным к родителю. Порталы решают обе проблемы.
События всё ещё всплывают
Несмотря на отрисовку в другом месте DOM, события React всплывают через дерево компонентов, а не через дерево DOM. Щелчок в портале по-прежнему всплывает до обработчиков onClick в родительских компонентах React.
Закрытие по щелчку на фоне
Остановите всплытие событий на содержимом модального окна, чтобы щелчки внутри него не закрывали окно.
function Modal({ onClose, children }) {
return createPortal(
<div className="backdrop" onClick={onClose}>
<div className="content" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>,
document.body
);
}Закрытие клавишей Escape
Добавляйте обработчик события keydown, пока модальное окно открыто, и удаляйте его при размонтировании.
useEffect(() => {
const onKey = (e) => { if (e.key === 'Escape') onClose(); };
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);Управление фокусом в порталах
При открытии модального окна переместите фокус внутрь, удерживайте фокус до закрытия, а затем восстановите его на элементе, открывшем окно. Используйте библиотеку вроде focus-trap-react, чтобы не реализовывать это самостоятельно.
Нативный элемент dialog
<dialog> в HTML изначально поддерживает позиционирование модального окна, фон, удержание фокуса и закрытие клавишей Escape. Для большинства модальных окон это лучшая отправная точка, чем портал.
function Modal({ open, onClose, children }) {
const ref = useRef(null);
useEffect(() => {
if (open) ref.current?.showModal();
else ref.current?.close();
}, [open]);
return (
<dialog ref={ref} onClose={onClose}>
{children}
</dialog>
);
}Подсказки и всплывающие окна
Подсказки позиционируются относительно элемента-якоря. Портал позволяет им выйти за пределы контекстов наложения. Объединяйте его с floating-ui для обнаружения столкновений, переворота и позиционирования.
import { useFloating, autoUpdate } from '@floating-ui/react';
function Tooltip({ children, content }) {
const { refs, floatingStyles } = useFloating({ whileElementsMounted: autoUpdate });
const [open, setOpen] = useState(false);
return (
<>
<button ref={refs.setReference} onMouseEnter={() => setOpen(true)} onMouseLeave={() => setOpen(false)}>
{children}
</button>
{open && createPortal(
<div ref={refs.setFloating} style={floatingStyles}>{content}</div>,
document.body
)}
</>
);
}Ограничение SSR
На сервере document не существует. Либо отрисовывайте его только на клиенте (проверяя typeof window), либо используйте dynamic из Next.js с параметром ssr:false.
Доступность: aria-modal
Контейнеры модальных окон должны иметь role="dialog", aria-modal="true" и aria-labelledby, указывающий на заголовок модального окна. Или используйте нативный <dialog>.
Быстрая проверка
Зачем использовать createPortal для модальных окон вместо их непосредственной отрисовки в дереве компонентов?
Итоги: порталы
createPortal(children, container) отрисовывает содержимое в другом месте DOM, сохраняя логическую структуру дерева React. Решает проблемы выхода за пределы overflow, transform и z-index. События по-прежнему всплывают через дерево React. Добавляйте закрытие по Escape и управление фокусом. Нативный <dialog> берёт на себя большую часть этой работы для модальных окон. Для подсказок используйте floating-ui. Не забывайте о SSR (проверяйте typeof window).
Изучай HTML с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 41
- Уроки
- 163
Часто задаваемые вопросы
Урок «Порталы для модальных окон и подсказок» бесплатный?
Да — полный текст урока «Порталы для модальных окон и подсказок» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Frontend Academy, подпишись на CoddyKit PRO. Курс Frontend Academy содержит 4 уроков всего.
Чему я научусь в уроке «Порталы для модальных окон и подсказок»?
Используйте ReactDOM.createPortal для рендеринга дочерних элементов в другом узле DOM, чтобы модальные окна и подсказки могли выходить за пределы контейнеров overflow:hidden Ты практикуешь Frontend Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Frontend Academy?
Предыдущий опыт не требуется. Frontend Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.
Сколько времени занимает урок «Порталы для модальных окон и подсказок»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Frontend Academy?
Да. Каждый урок Frontend Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Составные компоненты с Context
- Свойства рендеринга и шаблоны HOC
- Порталы для модальных окон и подсказок
- Границы ошибок