Dialog позиционирование

Floating UI — это современная библиотека для точного позиционирования всплывающих элементов, таких как диалоги, тултипы, меню и поповеры. Её ключевая задача — управление положением элемента относительно его reference element с учётом доступного пространства, перекрытий и динамических изменений DOM. Диалоги (dialogs) — один из самых распространённых сценариев использования.

Reference и Floating элементы

Для корректного позиционирования необходимо различать два типа элементов:

  • Reference element — элемент, относительно которого будет позиционироваться диалог (например, кнопка открытия).
  • Floating element — сам диалог, который перемещается на основе расчётов библиотеки.
import { computePosition, offset, flip, shift } from '@floating-ui/dom';

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

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

Здесь используется три middleware:

  • offset(8) — смещение диалога на 8 пикселей от reference.
  • flip() — автоматическая смена позиции, если нет места в выбранной стороне.
  • shift({ padding: 5 }) — корректировка позиции для предотвращения выхода за границы видимой области.

Placement — направление диалога

placement определяет начальное расположение диалога относительно reference. Возможные значения:

  • top, bottom, left, right — стандартные направления.

  • Добавление -start или -end управляет выравниванием по оси:

    • bottom-start — диалог появляется снизу, левый край совпадает с reference.
    • right-end — диалог справа, нижний край совпадает с reference.

Правильный выбор placement зависит от визуальных требований и доступного пространства.

Middleware для сложного поведения

Floating UI поддерживает цепочку middleware, которая позволяет комбинировать несколько правил позиционирования:

  1. offset(value) — задаёт расстояние между reference и floating элементом. Может принимать число или функцию для динамических смещений.

  2. flip(options) — меняет сторону, если на выбранной нет места. Настройки:

    • fallbackPlacements — массив альтернативных позиций.
    • padding — отступ от краёв видимой области.
  3. shift(options) — сдвигает элемент внутри видимой области.

    • padding гарантирует, что элемент не выйдет за границы viewport.
  4. autoUpdate(reference, floating, callback) — автоматически пересчитывает позицию при изменении размеров reference, floating или окна.

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

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

Управление динамическим содержимым

Диалоги часто имеют изменяемое содержимое, что влияет на их размеры. Для корректного позиционирования необходимо:

  • Использовать autoUpdate или слушатели изменений размеров (ResizeObserver).
  • Пересчитывать позицию после изменений контента.
  • В случае анимации появления использовать requestAnimationFrame для плавного позиционирования.

Примеры комплексного диалога

computePosition(reference, floating, {
  placement: 'top-end',
  middleware: [
    offset(12),
    flip({ fallbackPlacements: ['bottom-end', 'top-start'] }),
    shift({ padding: 8 })
  ]
}).then(({ x, y, placement }) => {
  floating.style.transform = `translate3d(${x}px, ${y}px, 0)`;
  floating.dataset.placement = placement;
});
  • translate3d обеспечивает GPU-ускорение анимаций.
  • dataset.placement хранит фактическое положение для CSS-стилей.

Интеграция с фреймворками

Для React, Vue или Svelte существуют обёртки Floating UI, которые упрощают синхронизацию с состоянием компонента:

  • useFloating в React позволяет получать координаты и middleware результаты как реактивные значения.
  • Автоматическая подписка на изменения размера и прокрутки исключает ручное управление DOM.

Советы по производительности

  • Ограничивать количество middleware: каждый дополнительный шаг увеличивает вычисления.
  • Использовать autoUpdate только при необходимости. Для статичных диалогов достаточно однократного расчёта.
  • Минимизировать частоту пересчёта при анимациях, объединяя вызовы в requestAnimationFrame.

Работа с модальными и нестандартными окнами

Диалог может быть внутри overflow: hidden контейнера или position: fixed модального окна. В таких случаях необходимо учитывать:

  • strategy: ‘fixed’ | ‘absolute’ — определяет метод позиционирования.

    • fixed — позиционирование относительно viewport, подходит для модальных окон.
    • absolute — относительно ближайшего offsetParent.
  • Контейнеры с трансформациями (transform) влияют на расчёт координат, поэтому Floating UI учитывает getBoundingClientRect() всех предков.

Выводы по позиционированию

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

  • Стороны открытия (placement).
  • Смещения (offset).
  • Ограничений видимой области (flip, shift).
  • Актуальности при изменениях контента и окна (autoUpdate).

Корректная комбинация middleware позволяет создать диалог, который всегда остаётся видимым и точно выравнивается относительно reference, даже при сложных интерфейсных сценариях.