Профилирование и метрики

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

Ключевой принцип работы Floating UI — разделение вычисления позиции от непосредственного отображения. Это достигается через core API, которое возвращает объект с координатами x и y, а также возможные смещения (offsets), которые позволяют адаптировать позицию под конкретные условия.


Core Concepts

Reference и Floating элементы

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

Пример базового вызова:

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

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

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

Вывод: computePosition вычисляет оптимальные координаты с учётом всех ограничений и может быть вызван повторно при изменении размера окна или содержимого.


Модульность и middleware

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

  • offset — добавляет смещение от якоря.
  • flip — изменяет позицию при недостатке места.
  • shift — корректирует положение, чтобы элемент не выходил за границы viewport.
  • size — динамически подбирает размеры floating-элемента.
  • arrow — позиционирует стрелку tooltip/drodpown относительно reference.

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

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

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

Динамическое обновление позиции

Для элементов с изменяющимся содержимым или при скролле удобно использовать autoUpdate, которая автоматически пересчитывает позицию:

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

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

Особенности: autoUpdate отслеживает изменения размеров, скролла и видимости элемента, что исключает ручное управление событиями resize/scroll.


Профилирование производительности

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

  • Частые вызовы computePosition на каждом кадре могут быть дорогостоящими.
  • Middleware flip и shift выполняют расчёт относительно размеров viewport и reference, что при сложной верстке увеличивает нагрузку.
  • Рекомендуется использовать throttling/debouncing при обработке событий скролла и ресайза.

Пример throttling:

import { computePosition, offset } from '@floating-ui/dom';
import throttle from 'lodash.throttle';

const updatePosition = throttle(() => {
  computePosition(reference, floating, { middleware: [offset(10)] })
    .then(({ x, y }) => {
      Object.assign(floating.style, { left: `${x}px`, top: `${y}px` });
    });
}, 50);

window.addEventListener('scroll', updatePosition);
window.addEventListener('resize', updatePosition);

Метрики и мониторинг

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

  • Время вычисления позиции — измеряется через performance.now() до и после вызова computePosition.
  • Количество перерисовок DOM — важно, чтобы не происходило лишних изменений стиля.
  • Количество middleware — каждое добавляет вычислительную нагрузку.
  • Частота обновлений позиции — напрямую влияет на FPS страницы.

Пример сбора метрик:

const start = performance.now();
computePosition(reference, floating, { middleware: [offset(10), flip()] })
  .then(() => {
    const duration = performance.now() - start;
    console.log(`Position computed in ${duration.toFixed(2)}ms`);
  });

Оптимизация при массовых элементах

  1. Использовать один autoUpdate на группу элементов, если возможно, а не по каждому элементу отдельно.
  2. Минимизировать middleware — применять только необходимые.
  3. Ленивая инициализация — вычислять позицию только при необходимости (например, при hover или focus).
  4. Кэширование размеров reference — если размеры не меняются, пересчёт можно пропускать.
  5. Использовать transform: translate3d для плавного позиционирования без перерасчета layout.

Примеры сложных сценариев

Tooltip с динамической длиной

computePosition(reference, floating, {
  placement: 'top',
  middleware: [
    offset(8),
    flip(),
    shift({ padding: 5 }),
    size({ apply({ rects, availableHeight }) {
      floating.style.maxHeight = `${availableHeight}px`;
    }})
  ]
});

Особенности: комбинация flip, shift и size позволяет tooltip адаптироваться под размеры экрана и длину контента.

autoUpdate(reference, floating, () => {
  computePosition(reference, floating, {
    middleware: [offset(4), flip(), shift()]
  }).then(({ x, y }) => {
    floating.style.transform = `translate3d(${x}px, ${y}px, 0)`;
  });
});

Вывод: использование translate3d повышает производительность анимаций и уменьшает влияние на layout, что критично для сложных интерфейсов.


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