Учет размеров стрелки

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


Свойство middleware для стрелки

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

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

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

computePosition(referenceElement, floatingElement, {
  middleware: [
    arrow({ element: arrowElement })
  ],
});
  • element — DOM-узел стрелки.
  • padding — расстояние от края поповера до стрелки. Указывается в пикселях и предотвращает попадание стрелки в углы.

Пример с padding:

arrow({ element: arrowElement, padding: 8 });

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


Расчет позиции стрелки

Middleware arrow возвращает объект с координатами x и y, которые затем нужно применить к стилям стрелки:

computePosition(referenceElement, floatingElement, {
  middleware: [arrow({ element: arrowElement })],
}).then(({ x, y }) => {
  Object.assign(arrowElement.style, {
    left: `${x}px`,
    top: `${y}px`,
  });
});

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


Учет размеров стрелки при смещении

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

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

computePosition(referenceElement, floatingElement, {
  middleware: [
    offset(10),          // отступ поповера от reference
    flip(),              // автоматический флип при выходе за viewport
    arrow({ element: arrowElement, padding: 5 })
  ],
});
  • offset добавляет пространство между поповером и стрелкой.
  • flip обеспечивает, что стрелка всегда указывает на видимую сторону reference.

При таком подходе стрелка корректно центрируется по оси основной позиции, даже если поповер был перевернут.


Управление размерами стрелки через CSS

Размер стрелки задается через CSS и влияет на вычисления Floating UI:

.arrow {
  width: 10px;
  height: 10px;
  position: absolute;
  background: inherit;
  transform: rotate(45deg);
}
  • Ширина и высота стрелки используются middleware для корректного смещения.
  • Поворот через transform: rotate(45deg) позволяет создавать классический ромбовидный вид.
  • background: inherit обеспечивает совпадение цвета с поповером.

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


Центрирование стрелки при динамическом контенте

Если контент поповера изменяется динамически (например, текст увеличивается), нужно пересчитывать позицию стрелки после изменения размеров:

function updatePosition() {
  computePosition(referenceElement, floatingElement, {
    middleware: [arrow({ element: arrowElement })],
  }).then(({ x, y }) => {
    Object.assign(arrowElement.style, { left: `${x}px`, top: `${y}px` });
  });
}

new ResizeObserver(updatePosition).observe(floatingElement);
  • ResizeObserver отслеживает изменение размеров поповера.
  • Пересчет координат стрелки обеспечивает постоянное центрирование относительно reference.

Советы по работе с большим popover

  1. Использовать padding у стрелки — избегает наложений на края.
  2. Сочетать middleware: offset + flip + arrow для гибкого позиционирования.
  3. Пересчитывать позиции при изменении размеров контента.
  4. Учитывать transform и border-radius — иногда влияет на визуальное смещение стрелки.
  5. Минимизировать сложные вложенные элементы внутри стрелки — это упрощает расчет координат.

Пример полного кода с учетом стрелки и flip

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

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

  const { x: arrowX, y: arrowY } = middlewareData.arrow;
  Object.assign(arrowEl.style, {
    left: arrowX != null ? `${arrowX}px` : '',
    top: arrowY != null ? `${arrowY}px` : '',
  });
});

Middleware возвращает объект middlewareData.arrow, содержащий точные координаты стрелки. Это позволяет корректно позиционировать стрелку независимо от выбранной стороны и размеров поповера.