В библиотеке Floating UI для работы с плавающими элементами часто возникает необходимость управлять производительностью при обработке событий, таких как скролл, ресайз или изменение состояния элементов. В этих случаях критически важны техники debouncing и throttling, которые позволяют контролировать частоту вызовов функций, предотвращая избыточные вычисления и улучшая отзывчивость интерфейса.
Debouncing — это метод, при котором функция выполняется только после того, как прошло определённое время с момента последнего вызова. Если событие повторяется до истечения таймера, предыдущий вызов отменяется.
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
В контексте Floating UI debouncing полезен для оптимизации позиционирования всплывающих элементов при изменении размеров окна или перемещении целевого элемента. Например:
import { computePosition } fr om '@floating-ui/dom';
const button = document.querySelector('#button');
const tooltip = document.querySelector('#tooltip');
const updatePosition = debounce(() => {
computePosition(button, tooltip, {
placement: 'top',
middleware: [offset(10), flip(), shift({ padding: 5 })],
}).then(({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}, 100);
window.addEventListener('resize', updatePosition);
window.addEventListener('scroll', updatePosition);
В этом примере функция updatePosition вызывается не чаще
одного раза каждые 100 мс после окончания событий, что предотвращает
чрезмерную нагрузку на браузер.
Throttling ограничивает частоту вызова функции, позволяя выполнять её не чаще одного раза в заданный интервал времени. В отличие от debouncing, throttling гарантирует регулярное выполнение функции при постоянном потоке событий.
function throttle(fn, lim it) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn.apply(this, args);
}
};
}
Для Floating UI throttling эффективен при отслеживании скролла страницы, когда требуется плавное, но ограниченное обновление позиции элементов:
const updateTooltipPosition = throttle(() => {
computePosition(button, tooltip, {
placement: 'top',
middleware: [offset(10), flip(), shift({ padding: 5 })],
}).then(({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
});
}, 50);
window.addEventListener('scroll', updateTooltipPosition);
Здесь обновление позиции всплывающего элемента будет происходить не чаще одного раза каждые 50 мс, что обеспечивает плавность и экономию ресурсов.
| Параметр | Debouncing | Throttling |
|---|---|---|
| Когда выполняется | После завершения серии событий | Периодически во время серии событий |
| Идеален для | Ввод текста, resize окна, завершение drag | Скролл, drag-and-drop, постоянные анимации |
| Основное преимущество | Сокращает ненужные вызовы после серии событий | Гарантирует регулярное выполнение функции |
В Floating UI debouncing часто применяется для редких изменений, например при ресайзе окна, чтобы не пересчитывать позиции каждый пиксель изменения. Throttling применяется для частых событий, таких как скролл или движение мыши, когда позиция элемента должна обновляться регулярно, но с ограничением частоты вызовов.
Для интеграции debouncing и throttling с Floating UI можно комбинировать их с системой autoUpdate, которая автоматически отслеживает изменения положения и размеров элементов:
import { autoUpdate, computePosition, offset, flip, shift } from '@floating-ui/dom';
const cleanup = autoUpdate(button, tooltip, debounce(() => {
computePosition(button, tooltip, {
placement: 'right',
middleware: [offset(8), flip(), shift({ padding: 4 })],
}).then(({ x, y }) => {
Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px` });
});
}, 100));
Использование debouncing или throttling в сочетании с
autoUpdate позволяет добиться максимальной
производительности при динамическом позиционировании и минимизировать
нагрузку на браузер, особенно при сложных интерфейсах с множеством
всплывающих элементов.
requestAnimationFrame
вместо фиксированного интервала, особенно для анимаций и плавного
скролла.Эти техники обеспечивают гибкое управление частотой вызовов функций, повышают производительность интерфейсов с плавающими элементами и предотвращают визуальные «подергивания» при частых изменениях DOM.