Когда создается всплывающий элемент, такой как тултип, поповер или меню, важным аспектом является корректное позиционирование относительно целевого элемента. Одним из критических компонентов является стрелка (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 и влияет на вычисления Floating UI:
.arrow {
width: 10px;
height: 10px;
position: absolute;
background: inherit;
transform: rotate(45deg);
}
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);
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,
содержащий точные координаты стрелки. Это позволяет корректно
позиционировать стрелку независимо от выбранной стороны и размеров
поповера.