Floating элементы внутри floating элементов

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

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

  • Родительский Floating элемент определяет базовую область позиционирования для дочернего.
  • Дочерний Floating элемент может иметь собственные стратегии позиционирования (absolute, fixed) и срабатывает на события взаимодействия с пользователем или обновления родителя.
  • Floating UI автоматически вычисляет координаты с учётом размеров viewport, scroll-контейнеров и других ограничений.

Стратегии позиционирования для вложенных элементов

  1. Absolute Используется, когда дочерний элемент должен располагаться относительно ближайшего позиционированного родителя. В контексте Floating UI это чаще всего reference element родительского Floating.

    import { computePosition, offset, flip, shift } from '@floating-ui/dom';
    
    const parent = document.querySelector('#parent');
    const child = document.querySelector('#child');
    
    computePosition(parent, child, {
      placement: 'bottom-start',
      middleware: [offset(5), flip(), shift({ padding: 8 })],
    }).then(({ x, y }) => {
      Object.assign(child.style, {
        left: `${x}px`,
        top: `${y}px`,
        position: 'absolute',
      });
    });
  2. Fixed Используется, когда элемент должен игнорировать скроллинг родителя. Вложенные Floating элементы с fixed позиционированием не наследуют смещения родителя.

Выбор стратегии зависит от поведения интерфейса: если дочерний элемент является частью выпадающего меню, лучше использовать absolute. Если это отдельный контекст, который должен оставаться на экране при скролле, применяют fixed.


Middleware для управления вложенными Floating

1. Offset Позволяет смещать дочерний элемент относительно родителя. При вложенности смещение может быть кумулятивным.

2. Flip Позволяет дочернему Floating элементу менять сторону, если родитель выходит за границы viewport. Важно учитывать, что Flip дочернего элемента срабатывает после вычисления позиции родителя.

3. Shift Сдвигает элемент внутрь видимой области. Вложенные элементы получают значения с учётом родителя, что предотвращает наложение за пределами экрана.

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


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

Вложенные Floating элементы часто требуют динамического обновления позиции при изменении размера или скролла родителя:

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

autoUpdate(parent, child, () => {
  computePosition(parent, child, {
    placement: 'bottom-start',
    middleware: [offset(5), flip(), shift()],
  }).then(({ x, y }) => {
    Object.assign(child.style, {
      left: `${x}px`,
      top: `${y}px`,
    });
  });
});
  • autoUpdate автоматически отслеживает скролл, ресайз окна и изменения размеров элементов.
  • Для вложенных элементов важно вызывать autoUpdate для каждого уровня иерархии, иначе позиция дочернего элемента может сбиваться при движении родителя.

Практические сценарии

  1. Выпадающие меню с подменю Родительское меню управляет положением верхнего уровня, подменю рассчитывает позицию относительно активного пункта:

    • Родитель: bottom-start
    • Дочерний: right-start с middleware flip() и shift()
  2. Tooltip внутри модального окна Tooltip позиционируется относительно кнопки внутри модального. Вложенность помогает учитывать смещения модального и viewport.

  3. Комбинированные контекстные панели Панели, которые открываются друг над другом, используют absolute позиционирование и middleware offset + flip. Важно правильно передавать reference element дочернего уровня.


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

  • Использовать autoUpdate только для видимых элементов, чтобы снизить нагрузку при множестве вложенных Floating.
  • Ограничивать количество middleware для каждого уровня, особенно flip и shift, так как они требуют сложных вычислений.
  • Для глубокой вложенности полезно сохранять координаты родителя и использовать их в вычислении дочернего элемента, вместо постоянного пересчёта через DOM.

Особенности z-index и stacking context

Вложенные Floating элементы требуют правильного управления слоёвостью:

  • Каждый дочерний элемент должен иметь z-index выше родителя, если требуется визуальное перекрытие.
  • Если родитель имеет CSS-свойство transform, дочерний absolute элемент будет позиционироваться относительно transformed ancestor, что важно учитывать при сложных интерфейсах.

Интеграция с React и Vue

Для компонентов React и Vue Floating UI предоставляет хуки:

  • useFloating — основной хук для позиции.
  • useInteractions — позволяет связывать клики, hover и фокус с отображением вложенных элементов.

Пример для React:

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

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

Дочерние элементы используют собственный useFloating с reference на родительский элемент, что создаёт иерархическую цепочку для правильного позиционирования.


Заключение по ключевым моментам

  • Вложенные Floating элементы строятся на иерархии reference elements.
  • Middleware offset, flip, shift, arrow применяются отдельно на каждом уровне.
  • Стратегия позиционирования (absolute или fixed) определяет зависимость от родителя и поведение при скролле.
  • Автоматическое обновление (autoUpdate) необходимо для динамических интерфейсов, но следует оптимизировать для производительности.
  • z-index и stacking context критичны для визуальной корректности.

Всё это позволяет создавать сложные интерфейсы с несколькими уровнями Floating элементов без нарушения UX и корректно обрабатывать динамические изменения DOM и viewport.