Открытие и закрытие floating элемента

Floating элементы — это панели, всплывающие окна, тултипы и дропдауны, которые позиционируются относительно другого элемента на странице. Важнейший аспект работы с ними — управление их видимостью, то есть открытием и закрытием. В библиотеке Floating UI это реализуется через комбинацию позиционирования, состояний и обработчиков событий.


Состояние видимости

Для управления отображением floating элемента используется состояние, чаще всего реализуемое через React useState или любой другой механизм управления состоянием:

import { useState } from "react";

const [isOpen, setIsOpen] = useState(false);
  • isOpen — булев флаг, определяющий, виден ли floating элемент.
  • setIsOpen — функция для изменения состояния.

Ключевая идея: состояние isOpen связывается с компонентом, который рендерит floating элемент, что позволяет контролировать его появление и исчезновение динамически.


Привязка к элементу-активатору

Floating элемент всегда позиционируется относительно активатора — кнопки, ссылки, иконки и т.п. Для этого используется реф активатора:

import { useRef } from "react";

const referenceRef = useRef(null);
  • referenceRef привязывается к DOM-элементу активатора.
  • Floating UI использует эту привязку для вычисления позиции floating элемента.

Открытие через событие

Открытие floating элемента обычно реализуется через событие click или hover:

  • При клике состояние isOpen переключается.
  • Можно добавить логическую проверку, чтобы предотвратить открытие в определённых условиях.

Для событий наведения (hover) используется комбинация onMouseEnter и onMouseLeave:

  • Это особенно важно для тултипов и всплывающих подсказок.
  • Для плавного закрытия можно использовать таймеры и задержку.

Закрытие через внешние клики

Floating UI предоставляет утилиты для закрытия floating элемента при клике вне его области. Обычно это реализуется с помощью хука useFloating:

import { useFloating, useClick, useDismiss } from "@floating-ui/react";

const { x, y, reference, floating, strategy } = useFloating({
  open: isOpen,
  onOpenChange: setIsOpen
});

useClick(reference, { enabled: true });
useDismiss(floating, { outsidePress: true });
  • useClick управляет открытием по клику.
  • useDismiss отвечает за закрытие при клике вне floating элемента или при нажатии клавиши Escape.

Особенность: хук useDismiss можно настраивать, например, отключить закрытие при клике на сам элемент.


Анимация открытия и закрытия

Для плавного появления floating элемента можно использовать CSS-переходы или анимации:

.floating {
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.floating.open {
  opacity: 1;
  transform: translateY(0);
}

И в компоненте:

Меню
  • Плавная анимация улучшает восприятие интерфейса.
  • Комбинируется с состоянием isOpen для управления классом open.

Сочетание с клавиатурной навигацией

Закрытие floating элемента через клавиши повышает доступность:

useEffect(() => {
  const handleKeyDown = (event) => {
    if (event.key === "Escape") setIsOpen(false);
  };
  document.addEventListener("keydown", handleKeyDown);
  return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
  • Поддержка клавиши Escape является стандартом для модальных и всплывающих элементов.
  • Можно расширять для стрелок навигации между элементами внутри floating.

Управление несколькими floating элементами

Когда на странице несколько floating элементов, важно управлять их приоритетом и состоянием:

  • Использовать отдельные состояния isOpen для каждого floating элемента.
  • Вызывать setIsOpen(false) на остальных элементах при открытии нового.
  • Настраивать useDismiss с учётом конкретного активатора.

Пример:

const handleOpenMenu = () => {
  setIsOpenMenu(true);
  setIsOpenTooltip(false);
};

Обработка кликов внутри floating элемента

Иногда нужно, чтобы клики внутри floating элемента не закрывали его. Для этого useDismiss поддерживает insidePress:

useDismiss(floating, {
  outsidePress: true,
  insidePress: false
});
  • outsidePress: true — закрывает элемент при клике вне.
  • insidePress: false — игнорирует клики внутри floating элемента.

Итоговая структура компонента

Полная интеграция открытия и закрытия выглядит так:

import { useState, useRef } from "react";
import { useFloating, useClick, useDismiss } from "@floating-ui/react";

function FloatingMenu() {
  const [isOpen, setIsOpen] = useState(false);
  const { x, y, reference, floating, strategy } = useFloating({
    open: isOpen,
    onOpenChange: setIsOpen
  });

  useClick(reference);
  useDismiss(floating, { outsidePress: true });

  return (
    <>
      
      {isOpen && (
        
  • Пункт 1
  • Пункт 2
  • Пункт 3
)} ); }
  • Обеспечивается корректное открытие и закрытие, привязка к активатору и правильное позиционирование.
  • Комбинация хуков useClick и useDismiss покрывает все стандартные сценарии взаимодействия.