Использование с координатами мыши

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

Создание элемента и установка позиции

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

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

Эти функции позволяют вычислять положение всплывающего элемента и применять стратегии корректировки позиции, такие как flip (смена стороны) и shift (подстройка внутри границ).

Привязка к координатам мыши

В отличие от стандартного позиционирования относительно DOM-элемента, здесь опорными точками будут координаты курсора:

const tooltip = document.querySelector('#tooltip');

document.addEventListener('mousemove', async (event) => {
  const { clientX: x, clientY: y } = event;

  const position = await computePosition(
    { getBoundingClientRect: () => ({ x, y, width: 0, height: 0 }) },
    tooltip,
    {
      middleware: [offset(10), flip(), shift({ padding: 5 })],
      strategy: 'fixed',
    }
  );

  Object.assign(tooltip.style, {
    left: `${position.x}px`,
    top: `${position.y}px`,
  });
});

Ключевые моменты этого подхода:

  • getBoundingClientRect используется для создания виртуального элемента с координатами мыши.
  • offset(10) добавляет небольшое смещение, чтобы всплывающий элемент не перекрывал курсор.
  • flip() и shift() автоматически корректируют положение, если элемент выходит за границы окна.
  • strategy: 'fixed' фиксирует элемент относительно окна браузера, а не документа.

Использование виртуального элемента

Floating UI поддерживает виртуальные элементы, что позволяет вычислять позицию без привязки к реальному DOM-узлу. В примере выше { getBoundingClientRect: () => ({ x, y, width: 0, height: 0 }) } — это виртуальный элемент.

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

const virtualMouse = {
  getBoundingClientRect: () => ({
    x: mouseX,
    y: mouseY,
    width: 0,
    height: 0,
    top: mouseY,
    left: mouseX,
    right: mouseX,
    bottom: mouseY,
  }),
  contextElement: document.body,
};

contextElement указывает, относительно какого элемента будут вычисляться позиции.

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

Постоянное обновление позиции на mousemove может вызывать нагрузку. Для оптимизации используют throttle или requestAnimationFrame:

let frame;
document.addEventListener('mousemove', (event) => {
  if (frame) cancelAnimationFrame(frame);
  frame = requestAnimationFrame(() => updateTooltip(event));
});

async function updateTooltip(event) {
  const { clientX: x, clientY: y } = event;
  const position = await computePosition(
    { getBoundingClientRect: () => ({ x, y, width: 0, height: 0 }) },
    tooltip,
    {
      middleware: [offset(10), flip(), shift({ padding: 5 })],
      strategy: 'fixed',
    }
  );
  Object.assign(tooltip.style, {
    left: `${position.x}px`,
    top: `${position.y}px`,
  });
}

Это гарантирует, что вычисление позиции не выполняется чаще, чем частота перерисовки экрана, предотвращая лаги при быстром движении мыши.

Смешанное позиционирование

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

const button = document.querySelector('#button');

button.addEventListener('click', async (event) => {
  const virtual = {
    getBoundingClientRect: () => ({
      x: event.clientX,
      y: event.clientY,
      width: 0,
      height: 0,
    }),
  };

  const position = await computePosition(virtual, tooltip, {
    middleware: [offset(5), flip(), shift({ padding: 8 })],
    strategy: 'absolute',
  });

  Object.assign(tooltip.style, {
    left: `${position.x}px`,
    top: `${position.y}px`,
  });
});

Такой подход объединяет контекст кнопки (для ограничений) и координаты мыши для точного открытия.

Обработка динамических изменений окна

При изменении размера окна или прокрутке важно пересчитывать позицию. Floating UI поддерживает динамическую адаптацию через вызов computePosition на события resize и scroll. Пример с использованием mousemove:

window.addEventListener('resize', () => updateTooltip(lastEvent));
window.addEventListener('scroll', () => updateTooltip(lastEvent));

lastEvent — объект последнего события мыши, чтобы позиция оставалась корректной.


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