Frontend Academy · Урок

Порталы для модальных окон и подсказок

Используйте ReactDOM.createPortal для рендеринга дочерних элементов в другом узле DOM, чтобы модальные окна и подсказки могли выходить за пределы контейнеров overflow:hidden

Урок 3 из 414 шагов

«Порталы для модальных окон и подсказок» — бесплатный урок 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 — локальная установка не требуется.

Все уроки этого курса

  1. Составные компоненты с Context
  2. Свойства рендеринга и шаблоны HOC
  3. Порталы для модальных окон и подсказок
  4. Границы ошибок
← Назад к Frontend Academy