Micro-frontends

Floating UI — это современная библиотека для управления позиционированием плавающих элементов в веб-приложениях. Она заменяет устаревшие решения вроде Popper.js и предоставляет гибкий API для динамического расчёта координат элементов относительно якоря. Основная задача библиотеки — точное позиционирование всплывающих элементов (tooltip, popover, dropdown, меню и др.) с учётом ограничений видимой области и смещений.

Принцип работы

Floating UI строится на двух ключевых понятиях:

  1. Reference element — элемент, относительно которого позиционируется плавающий блок.
  2. Floating element — сам плавающий блок, координаты которого вычисляются динамически.

Библиотека использует функцию вычисления позиции computePosition(reference, floating, options), которая возвращает объект с координатами { x, y } и дополнительной информацией о размещении (placement, middlewareData).

import { computePosition, offset, flip } from '@floating-ui/dom';

const reference = document.querySelector('#button');
const floating = document.querySelector('#tooltip');

computePosition(reference, floating, {
  placement: 'top',
  middleware: [offset(8), flip()]
}).then(({ x, y }) => {
  Object.assign(floating.style, {
    left: `${x}px`,
    top: `${y}px`
  });
});

В этом примере offset(8) добавляет отступ от якоря, а flip() автоматически переворачивает элемент, если он не помещается на экране.

Middleware: расширяемое позиционирование

Middleware — ключевой инструмент для расширения логики расчёта позиции. Библиотека предоставляет набор готовых middleware и позволяет создавать свои:

  • offset(value) — смещение плавающего элемента относительно якоря. Может принимать число, массив или функцию.
  • flip(options) — автоматически меняет позицию при недостатке места, можно настроить fallback-плейсменты.
  • shift(options) — смещает элемент внутри видимой области, предотвращая выход за границы окна.
  • size(options) — автоматически изменяет размер плавающего блока для соответствия ограниченной области.
  • arrow(options) — корректирует позицию стрелки относительно якоря и плавающего блока.

Middleware вызываются последовательно и могут модифицировать финальные координаты x и y. Понимание порядка вызова важно при комбинировании нескольких middleware.

computePosition(reference, floating, {
  placement: 'right',
  middleware: [
    offset(10),
    flip({ fallbackPlacements: ['left', 'bottom'] }),
    shift({ padding: 5 })
  ]
});

React и Floating UI

Для работы в React Floating UI предлагает хук useFloating, который инкапсулирует вычисление позиции и управление состоянием:

import { useFloating, offset, flip, shift } from '@floating-ui/react-dom';

function Tooltip({ children, label }) {
  const { x, y, reference, floating, strategy } = useFloating({
    placement: 'top',
    middleware: [offset(5), flip(), shift()]
  });

  return (
    <>
      <button ref={reference}>{children}</button>
      <div
        ref={floating}
        style={{ position: strategy, top: y ?? 0, left: x ?? 0 }}
      >
        {label}
      </div>
    </>
  );
}

Здесь strategy задаёт способ позиционирования: absolute или fixed. В React контексте Floating UI автоматически пересчитывает координаты при изменении DOM и размеров элементов.

Интеграция с Micro-Frontends

Micro-frontends требуют, чтобы плавающие элементы корректно работали в разных фреймах или Shadow DOM. Floating UI поддерживает:

  1. Shadow DOM — можно вычислять позицию относительно элементов внутри теневого дерева с помощью getBoundingClientRect() и передачи кастомного контейнера.
  2. Cross-frame позиционирование — библиотека позволяет учитывать смещения родительских окон или iframe.
  3. Изоляция стилей — Floating UI не зависит от глобальных CSS, позволяя управлять позиционированием в пределах изолированного micro-frontend.

Пример использования в Shadow DOM:

const shadowRoot = hostElement.attachShadow({ mode: 'open' });
const button = document.createElement('button');
shadowRoot.appendChild(button);

const tooltip = document.createElement('div');
shadowRoot.appendChild(tooltip);

computePosition(button, tooltip, { placement: 'bottom' })
  .then(({ x, y }) => {
    tooltip.style.transform = `translate(${x}px, ${y}px)`;
  });

Оптимизация производительности

Floating UI минимизирует количество перерасчётов позиции через:

  • autoUpdate — функция, которая подписывает элементы на события изменения размеров, скролла или трансформаций:
import { autoUpdate } from '@floating-ui/dom';

autoUpdate(reference, floating, () => {
  computePosition(reference, floating, { middleware: [offset(5), flip()] })
    .then(({ x, y }) => {
      Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
    });
});
  • Дебаунс вычислений — особенно важно в micro-frontends, где множество небольших фреймов могут генерировать события одновременно.
  • Lazy-rendering — плавающий элемент создаётся и позиционируется только при необходимости, что снижает нагрузку на рендер.

Дополнительные возможности

  • Virtual elements — Floating UI может позиционировать элементы относительно несуществующих DOM-объектов, что полезно для кастомных курсоров или выделений текста.
  • Сложные комбинации middleware — можно комбинировать flip, shift, size и arrow для создания адаптивных всплывающих интерфейсов.
  • Поддержка TypeScript — библиотека полностью типизирована, что облегчает разработку в крупных приложениях.

Практические рекомендации

  • Всегда задавать middleware в порядке от смещения к адаптивному изменению позиции (offset → flip → shift → arrow).
  • Использовать autoUpdate при динамическом контенте, особенно если родительский фрейм или DOM элементы могут изменять размер.
  • Для Micro-frontends предпочтительно использовать Shadow DOM или изолированные контейнеры, чтобы избежать конфликтов стилей и корректно рассчитывать координаты.
  • Проверять fallbackPlacements для flip, чтобы элементы никогда не вылезали за пределы видимой области.

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