Context menu

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


Основы работы с Floating UI для контекстного меню

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

  • reference — элемент, относительно которого будет позиционироваться меню.
  • floating — само всплывающее меню.

Простейший пример:

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

const reference = document.querySelector('#target');
const floating = document.querySelector('#context-menu');

reference.addEventListener('contextmenu', async (event) => {
  event.preventDefault();

  const { x, y } = await computePosition(reference, floating, {
    middleware: [
      offset(4),  // отступ от якоря
      flip(),     // автоматический переворот, если меню не помещается
      shift()     // корректировка позиции при выходе за границы окна
    ],
  });

  Object.assign(floating.style, {
    left: `${x}px`,
    top: `${y}px`,
    position: 'absolute',
    display: 'block'
  });
});

Ключевые моменты:

  • offset добавляет смещение от якоря, чтобы меню не накладывалось на элемент.
  • flip обеспечивает видимость меню при нехватке места с одной стороны.
  • shift предотвращает выход меню за границы окна.

Контекстное меню на произвольной позиции курсора

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

const virtualReference = {
  getBoundingClientRect: () => ({
    x: event.clientX,
    y: event.clientY,
    width: 0,
    height: 0,
    top: event.clientY,
    left: event.clientX,
    right: event.clientX,
    bottom: event.clientY
  }),
  contextElement: reference
};

Далее virtualReference передается в computePosition вместо реального DOM-узла:

const { x, y } = await computePosition(virtualReference, floating, {
  middleware: [offset(4), flip(), shift()]
});

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


Управление поведением меню

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

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

autoUpdate(reference, floating, () => {
  computePosition(reference, floating, {
    middleware: [offset(4), flip(), shift()]
  }).then(({ x, y }) => {
    Object.assign(floating.style, {
      left: `${x}px`,
      top: `${y}px`
    });
  });
});
  • autoUpdate следит за изменениями и автоматически пересчитывает позицию.
  • Это особенно полезно для меню, которое должно оставаться привязанным к элементу при прокрутке или ресайзе.

Расширенные возможности: Middleware

Middleware — это мощный инструмент для кастомизации поведения всплывающих элементов:

  1. offset — задает отступ от якоря.
  2. flip — меняет сторону появления, если не хватает места.
  3. shift — корректирует координаты для попадания в видимую область.
  4. arrow — позволяет позиционировать стрелку меню, которая указывает на якорь.

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

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

const arrowElement = document.querySelector('.arrow');

computePosition(reference, floating, {
  middleware: [offset(8), flip(), shift(), arrow({ element: arrowElement })]
}).then(({ x, y, middlewareData }) => {
  const { x: arrowX, y: arrowY } = middlewareData.arrow;
  Object.assign(arrowElement.style, {
    left: arrowX != null ? `${arrowX}px` : '',
    top: arrowY != null ? `${arrowY}px` : ''
  });

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

Стрелка автоматически выравнивается относительно меню и его якоря, делая интерфейс интуитивным.


Анимация и плавное открытие

Floating UI не отвечает за анимацию, но легко интегрируется с CSS-переходами:

.context-menu {
  transition: opacity 0.2s ease, transform 0.2s ease;
  opacity: 0;
  transform: scale(0.95);
}

.context-menu.show {
  opacity: 1;
  transform: scale(1);
}

В Jav * aScript:

floating.classList.add('show'); // появляется с анимацией
floating.classList.remove('show'); // скрытие с анимацией

Сочетание точного позиционирования Floating UI и плавной анимации CSS обеспечивает современное ощущение интерфейса.


Реактивное закрытие меню

Меню должно закрываться при клике вне или при выборе пункта:

document.addEventListener('click', (event) => {
  if (!floating.contains(event.target)) {
    floating.style.display = 'none';
  }
});

Также можно закрывать меню при скролле или изменении размеров окна, используя ту же функцию autoUpdate с обратным вызовом для скрытия.


Пример комплексного контекстного меню

Комбинируя все описанные техники:

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

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