Специфика размещения

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

Базовая функция позиционирования:

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

computePosition(referenceEl, floatingEl).then(({ x, y }) => {
  Object.assign(floatingEl.style, {
    left: `${x}px`,
    top: `${y}px`,
  });
});

Здесь:

  • referenceEl — элемент-источник (якорь)
  • floatingEl — плавающий элемент
  • x, y — вычисленные координаты

Система размещения (placement)

Placement определяет, где относительно reference-элемента будет находиться floating-элемент.

Базовые направления:

  • top
  • bottom
  • left
  • right

С выравниванием:

  • top-start
  • top-end
  • bottom-start
  • bottom-end и т.д.

Пример:

computePosition(referenceEl, floatingEl, {
  placement: 'bottom-start',
});

Выравнивание:

  • start — по левому или верхнему краю
  • end — по правому или нижнему краю

Особенность: направление зависит от writing mode (LTR/RTL).


Коллизии и ограничение области

Размещение не ограничивается простым выбором стороны. Floating UI учитывает:

  • границы viewport
  • scroll-контейнеры
  • clipping ancestors (родители с overflow)

Если элемент не помещается, используются middleware для корректировки.


Middleware: управление размещением

Middleware — цепочка функций, модифицирующих результат позиционирования.

Подключение:

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

computePosition(referenceEl, floatingEl, {
  middleware: [
    offset(8),
    flip(),
    shift(),
  ],
});

Смещение (offset)

Позволяет задать отступ между элементами.

offset(10)

Можно задавать:

  • число — основной отступ
  • объект — более тонкая настройка
offset({
  mainAxis: 10,
  crossAxis: 5,
});

Где:

  • mainAxis — направление размещения (например, вниз для bottom)
  • crossAxis — перпендикулярная ось

Автоматический переворот (flip)

Меняет сторону, если элемент не помещается.

flip()

Пример:

  • задано bottom
  • не хватает места
  • автоматически становится top

Расширенная настройка:

flip({
  fallbackPlacements: ['top', 'right'],
});

Сдвиг внутри границ (shift)

Предотвращает выход за пределы viewport.

shift()

Работает даже при фиксированном placement:

  • сохраняет сторону
  • корректирует координаты

Настройка:

shift({
  padding: 8,
});

Комбинация middleware

Порядок middleware критически важен:

middleware: [
  offset(8),
  flip(),
  shift(),
]

Почему:

  1. сначала отступ
  2. затем проверка на переполнение
  3. затем коррекция координат

Неверный порядок может привести к визуальным артефактам.


Ограничение размеров (size)

Позволяет адаптировать размеры floating-элемента под доступное пространство.

import { size } from '@floating-ui/dom';

size({
  apply({ availableWidth, availableHeight, elements }) {
    Object.assign(elements.floating.style, {
      maxWidth: `${availableWidth}px`,
      maxHeight: `${availableHeight}px`,
    });
  },
});

Используется для:

  • dropdown списков
  • модальных окон
  • адаптивных popover

Привязка к стрелке (arrow)

Часто требуется визуальная стрелка, указывающая на reference.

import { arrow } from '@floating-ui/dom';

const arrowEl = document.querySelector('.arrow');

computePosition(referenceEl, floatingEl, {
  middleware: [
    arrow({ element: arrowEl }),
  ],
});

Результат содержит координаты стрелки:

.then(({ middlewareData }) => {
  const { x, y } = middlewareData.arrow;

  Object.assign(arrowEl.style, {
    left: x != null ? `${x}px` : '',
    top: y != null ? `${y}px` : '',
  });
});

Особенность:

  • стрелка позиционируется относительно floating-элемента, а не страницы

Inline positioning (inline)

Используется для inline-элементов (например, текстовых ссылок, разбитых на несколько строк).

import { inline } from '@floating-ui/dom';

inline()

Решает проблему:

  • reference состоит из нескольких прямоугольников
  • стандартный bounding box даёт неточную позицию

Автоматическое обновление (autoUpdate)

При прокрутке, ресайзе или изменении DOM необходимо пересчитывать позицию.

import { autoUpdate } from '@floating-ui/dom';

const cleanup = autoUpdate(referenceEl, floatingEl, () => {
  computePosition(referenceEl, floatingEl).then(...);
});

Отслеживает:

  • scroll
  • resize
  • изменения layout

Остановка:

cleanup();

Работа с transform вместо top/left

Floating UI может использовать CSS transform:

Object.assign(floatingEl.style, {
  transform: `translate(${x}px, ${y}px)`,
});

Преимущества:

  • лучшая производительность
  • меньше layout-пересчётов

Позиционирование внутри порталов

Floating UI корректно работает даже если:

  • reference и floating находятся в разных DOM-ветках
  • используется React Portal

Это достигается через:

  • вычисление координат относительно viewport
  • учёт scroll-контейнеров

Стратегии позиционирования (strategy)

Два режима:

strategy: 'absolute'

или

strategy: 'fixed'

absolute:

  • относительно ближайшего позиционированного родителя
  • зависит от scroll-контейнеров

fixed:

  • относительно viewport
  • игнорирует прокрутку родителей

Выбор:

  • tooltip — обычно absolute
  • модальные/overlay — чаще fixed

Влияние scroll-контейнеров

Floating UI учитывает:

  • overflow: auto
  • overflow: hidden
  • вложенные прокрутки

Это важно, так как:

  • обычные вычисления через getBoundingClientRect могут быть недостаточны
  • библиотека корректирует координаты с учётом всех уровней вложенности

Адаптация под RTL

Placement автоматически адаптируется:

  • start и end меняются местами
  • учитывается direction: rtl

Это избавляет от ручной логики.


Расширяемость middleware

Можно писать собственные middleware:

const customMiddleware = {
  name: 'custom',
  fn(state) {
    return {
      ...state,
      x: state.x + 10,
    };
  },
};

Подключение:

middleware: [customMiddleware]

Частые проблемы и особенности

1. Дёргание позиции Причина:

  • постоянные перерасчёты
  • неправильный autoUpdate

Решение:

  • дебаунс
  • отключение лишних observers

2. Неправильное выравнивание Причина:

  • забытый shift
  • некорректный placement

3. Выход за границы Причина:

  • отсутствие flip или shift

Архитектурные особенности

Floating UI:

  • не зависит от фреймворка
  • работает как вычислительный движок
  • не управляет DOM напрямую

Это даёт:

  • гибкость
  • возможность интеграции с React, Vue, Svelte

Итоговая схема размещения

  1. Выбор placement
  2. Применение offset
  3. Проверка границ (flip)
  4. Коррекция (shift)
  5. Ограничение размеров (size)
  6. Дополнительные модификации (arrow, inline)

Каждый этап влияет на финальные координаты, формируя адаптивную систему позиционирования, устойчивую к изменениям интерфейса и окружения.