Сложные layouts

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


Контекст и стратегия позиционирования

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

  1. Выбор позиции относительно якоря – определение, где будет находиться плавающий элемент (top, bottom, left, right, auto).
  2. Коррекция позиции при переполнении – предотвращение выхода элемента за границы viewport или контейнера.

Для сложных layouts важно учитывать:

  • Наличие нескольких якорей и вложенных элементов.
  • Динамическое изменение размеров окна или содержимого.
  • Совмещение нескольких middleware, таких как flip, shift и offset.
import { computePosition, offset, flip, shift } from '@floating-ui/dom';

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

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

В этом примере комбинация middleware обеспечивает: отступ, автоматическую смену позиции при нехватке места и корректировку с учётом границ.


Работа с вложенными плавающими элементами

В сложных интерфейсах часто возникает ситуация, когда один floating элемент находится внутри другого. Например, dropdown внутри модального окна. В таких случаях важно:

  • Избегать накопления смещений, вызванного несколькими уровнями позиционирования.
  • Использовать computePosition рекурсивно или autoUpdate, чтобы элементы реагировали на изменения родителей.
import { autoUpdate } from '@floating-ui/dom';

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

autoUpdate отслеживает изменения размеров и положения родителя и автоматически пересчитывает координаты дочернего элемента.


Управление ограничениями контейнера

В сложных layouts нередко требуется, чтобы плавающие элементы не выходили за пределы определенного контейнера, а не всего viewport. Для этого Floating UI предоставляет middleware limitShift и size:

  • shift — сдвигает элемент, чтобы он не выходил за границы viewport.
  • limitShift — ограничивает сдвиг определёнными значениями.
  • size — динамически подстраивает размеры floating элемента под контейнер.
computePosition(reference, floating, {
  placement: 'bottom-start',
  middleware: [
    offset(5),
    shift({ boundary: document.querySelector('#container') }),
    size({ apply({ rects, availableHeight }) {
      floating.style.height = `${availableHeight}px`;
    }})
  ]
});

Такой подход позволяет создавать dropdown’ы и всплывающие окна, полностью адаптированные под сложные контейнерные структуры.


Композиция нескольких middleware

Для сложных интерфейсов middleware можно комбинировать, создавая цепочки трансформаций:

  1. offset — задаёт базовый отступ.
  2. flip — меняет сторону, если места недостаточно.
  3. shift — корректирует положение внутри ограничений.
  4. size — подстраивает размеры.
  5. arrow — позиционирует стрелку относительно якоря.
import { arrow } from '@floating-ui/dom';

computePosition(reference, floating, {
  placement: 'top',
  middleware: [
    offset(10),
    flip(),
    shift({ padding: 5 }),
    arrow({ element: floating.querySelector('.arrow') })
  ]
});

Комбинация middleware позволяет строить гибкие и надёжные плавающие компоненты даже в самых запутанных layouts.


Реактивные интерфейсы и обновление позиций

Для интерфейсов с анимацией или динамическим контентом важно поддерживать актуальное позиционирование:

  • autoUpdate — наблюдает за изменениями размеров и положения элементов.
  • Реактивные фреймворки (React, Vue) — можно интегрировать через хуки (useFloating в React) для автоматического обновления.
import { useFloating } from '@floating-ui/react';

const { x, y, reference, floating } = useFloating({
  placement: 'bottom',
  middleware: [offset(8), flip(), shift({ padding: 5 })]
});

Использование реактивного подхода упрощает работу с динамическими интерфейсами, где содержимое элементов постоянно меняется.


Особенности работы с портальными элементами

Когда floating элемент рендерится в другом месте DOM, необходимо корректно учитывать координаты viewport и скролла. Floating UI поддерживает работу с порталами с помощью передачи strategy: 'fixed' или strategy: 'absolute':

  • absolute — позиционирование относительно ближайшего позиционированного предка.
  • fixed — позиционирование относительно viewport, игнорируя скролл.
computePosition(reference, floating, {
  placement: 'bottom',
  strategy: 'fixed',
  middleware: [offset(5), flip()]
});

Выбор стратегии критичен для модальных окон, тултипов и сложных nested dropdown’ов.