Portal

Portal в Chakra UI используется для рендеринга компонентов вне текущей иерархии DOM, что особенно важно для модальных окон, тултипов, всплывающих уведомлений и других элементов, которые должны отображаться поверх всего остального контента. Основное преимущество Portals заключается в том, что они позволяют избежать проблем с CSS-стилями родительских компонентов, такими как overflow: hidden, z-index или position: relative.

В Chakra UI Portal реализован как компонент-обёртка, который автоматически помещает дочерние элементы в корень документа (document.body), обеспечивая корректное отображение поверх остальных компонентов.


Импорт и базовое использование

Чтобы использовать Portal, необходимо импортировать компонент из Chakra UI:

import { Portal, Box, Button } from '@chakra-ui/react';

Простейший пример:

<Box position="relative" width="200px" height="200px" bg="blue.100">
  Контент внутри Box
  <Portal>
    <Box position="absolute" top="0" left="0" bg="red.300" p={4}>
      Контент в Portal
    </Box>
  </Portal>
</Box>

В этом примере красный Box будет отрисован в document.body, а не внутри синего контейнера, что позволяет избежать ограничений по позиционированию.


Свойства компонента Portal

Chakra UI предоставляет минимальное количество свойств для Portal, что делает его легким и удобным:

  • children — React-элементы, которые будут рендериться внутри Portal.
  • containerRef — опционально, ссылка на DOM-элемент, куда будет рендериться Portal. По умолчанию это document.body.

Пример с кастомным контейнером:

import { useRef } from 'react';

const CustomPortal = () => {
  const containerRef = useRef();

  return (
    <>
      <Box ref={containerRef} position="relative" height="300px" bg="green.50">
        Контейнер для Portal
      </Box>
      <Portal containerRef={containerRef}>
        <Box position="absolute" top="10px" left="10px" bg="yellow.300" p={2}>
          Портал внутри кастомного контейнера
        </Box>
      </Portal>
    </>
  );
};

Использование с модальными окнами и всплывающими элементами

Portal часто применяют для реализации модальных окон, тултипов и попапов. Chakra UI автоматически использует Portal в своих компонентах Modal, Popover, Tooltip, что обеспечивает корректное отображение поверх остального интерфейса без необходимости ручного управления z-index.

Пример модального окна с использованием Portal вручную:

import { useState } from 'react';
import { Box, Button, Portal } from '@chakra-ui/react';

const ManualModal = () => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <>
      <Button onCl ick={() => setIsOpen(true)}>Открыть модальное окно</Button>
      {isOpen && (
        <Portal>
          <Box
            position="fixed"
            top="0"
            left="0"
            width="100vw"
            height="100vh"
            bg="blackAlpha.600"
            display="flex"
            alignItems="center"
            justifyContent="center"
          >
            <Box bg="white" p={6} borderRadius="md">
              Содержимое модального окна
              <Button mt={4} onCl ick={() => setIsOpen(false)}>
                Закрыть
              </Button>
            </Box>
          </Box>
        </Portal>
      )}
    </>
  );
};

Здесь Portal гарантирует, что модальное окно будет поверх всего интерфейса, не зависимо от родительских контейнеров и их стилей.


Вложенные и динамические Portals

Chakra UI позволяет создавать несколько Portals одновременно. Это полезно, когда требуется рендерить несколько всплывающих элементов в разных частях страницы. Порядок рендеринга определяется порядком в DOM, а Chakra автоматически заботится о корректной вставке.

Пример нескольких динамических Portals:

import { useState } from 'react';
import { Portal, Box, Button } from '@chakra-ui/react';

const MultiplePortals = () => {
  const [showFirst, setShowFirst] = useState(true);
  const [showSecond, setShowSecond] = useState(true);

  return (
    <>
      <Button onCl ick={() => setShowFirst(!showFirst)}>Toggle Первый Portal</Button>
      <Button ml={2} onCl ick={() => setShowSecond(!showSecond)}>Toggle Второй Portal</Button>

      {showFirst && (
        <Portal>
          <Box position="fixed" top="20px" left="20px" bg="red.300" p={2}>
            Первый Portal
          </Box>
        </Portal>
      )}

      {showSecond && (
        <Portal>
          <Box position="fixed" bottom="20px" right="20px" bg="blue.300" p={2}>
            Второй Portal
          </Box>
        </Portal>
      )}
    </>
  );
};

Взаимодействие с анимациями и фокусом

Так как Portal рендерит элементы вне родительской иерархии, нужно учитывать:

  1. Анимации — используют motion.div из framer-motion или Transition из Chakra UI для плавного появления/исчезновения.
  2. Фокусировка — при модальных окнах важно управлять фокусом вручную или через хуки useFocusLock, чтобы пользователи не могли взаимодействовать с элементами позади Portal.

Пример анимации с Chakra UI и Portal:

import { motion } from 'framer-motion';
import { Portal, Box } from '@chakra-ui/react';

const AnimatedPortal = () => (
  <Portal>
    <motion.div
      initial={{ opacity: 0, y: -20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
    >
      <Box bg="purple.300" p={4} borderRadius="md">
        Анимированный Portal
      </Box>
    </motion.div>
  </Portal>
);

Рекомендации по использованию Portal

  • Использовать Portal для элементов, которые должны отображаться поверх всего интерфейса.
  • Для стандартных всплывающих компонентов Chakra UI, таких как Modal, Tooltip, Popover, ручное использование Portal не требуется.
  • При сложных интерфейсах с кастомными контейнерами и динамическими элементами можно использовать containerRef для контроля, куда рендерится Portal.
  • Всегда учитывать доступность: модальные окна должны блокировать взаимодействие с контентом позади, а тултипы — иметь правильные ARIA-атрибуты.

Portal в Chakra UI — мощный инструмент, который обеспечивает чистую структуру DOM, стабильное позиционирование и совместимость с анимациями и фокусом. Его грамотное использование позволяет создавать сложные интерфейсы без проблем с перекрытием и стилями.