Throttle — ограничение частоты выполнения функции, при котором вызовы
происходят не чаще заданного интервала времени. В контексте анимаций и
событий браузера это один из ключевых инструментов управления
производительностью, особенно при работе с высокочастотными событиями
вроде scroll, resize,
pointermove.
При использовании Motion One и других библиотек, основанных на Web Animations API, проблема избыточных вызовов проявляется особенно остро: каждый лишний пересчёт стилей или обновление прогресса анимации увеличивает нагрузку на main thread, что приводит к пропускам кадров и нестабильной визуальной плавности.
Throttle фиксирует минимальный интервал между вызовами функции. Независимо от того, сколько событий произошло, функция выполнится не чаще одного раза за указанный период.
Базовая реализация:
function throttle(fn, limit) {
let lastCall = 0;
return function (...args) {
const now = Date.now();
if (now - lastCall >= limit) {
lastCall = now;
fn.apply(this, args);
}
};
}
Такой подход используется в обработчиках событий, где важно поддерживать регулярное обновление состояния, но без перегрузки:
window.addEventListener(
"scroll",
throttle(() => {
console.log(window.scrollY);
}, 100)
);
В анимационных сценариях throttle применяется для:
В современных интерфейсах часто используется гибридный подход, где
throttle заменяется или дополняется requestAnimationFrame.
Это позволяет синхронизировать обновления с кадровым циклом
браузера.
function rafThrottle(fn) {
let ticking = false;
let lastArgs;
return function (...args) {
lastArgs = args;
if (!ticking) {
ticking = true;
requestAnimationFrame(() => {
fn.apply(this, lastArgs);
ticking = false;
});
}
};
}
Такой механизм особенно полезен при интеграции с Motion One, где обновление параметров анимации должно происходить строго один раз за кадр.
Debounce обеспечивает выполнение функции только после того, как серия вызовов прекратилась на заданный промежуток времени. В отличие от throttle, здесь акцент не на регулярности, а на стабилизации финального состояния.
Базовая реализация:
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
Использование:
window.addEventListener(
"resize",
debounce(() => {
console.log("resize stabilized");
}, 200)
);
Debounce критичен в случаях, когда промежуточные значения не имеют смысла:
При работе с Motion One debounce применяется для ситуаций, где анимация зависит от итогового состояния, а не промежуточных изменений.
Пример: пересчёт позиции элементов после изменения layout:
window.addEventListener(
"resize",
debounce(() => {
recomputeLayout();
animateElements();
}, 250)
);
Throttle и debounce решают разные задачи управления потоком событий:
В анимационных системах это приводит к принципиально разным эффектам:
Throttle:
Debounce:
Motion One опирается на Web Animations API и requestAnimationFrame для синхронизации анимаций. Однако внешние события, управляющие анимацией, часто требуют дополнительного контроля частоты.
Пример интеграции throttle с управлением прогрессом анимации:
import { animate } from "motion";
const controls = animate(0, 1, {
duration: 1,
onUpdate: (latest) => {
console.log(latest);
}
});
window.addEventListener(
"scroll",
rafThrottle(() => {
const progress =
window.scrollY / (document.body.scrollHeight - innerHeight);
controls.currentTime = controls.duration * progress;
})
);
В этом сценарии throttle через requestAnimationFrame
предотвращает избыточные пересчёты currentTime, обеспечивая
стабильную синхронизацию scroll → animation.
Debounce используется для пересоздания или переинициализации анимаций:
import { animate } from "motion";
let animation;
function initAnimation() {
animation?.cancel();
animation = animate(".box", {
transform: ["translateY(0px)", "translateY(200px)"]
});
}
window.addEventListener("resize", debounce(initAnimation, 200));
События pointermove создают один из самых высоких
потоков вызовов в браузере. Без throttle они быстро приводят к
деградации производительности.
window.addEventListener(
"pointermove",
rafThrottle((e) => {
const x = e.clientX;
const y = e.clientY;
document.documentElement.style.setProperty("--x", x);
document.documentElement.style.setProperty("--y", y);
})
);
При использовании Motion One такие координаты часто применяются для:
Одной из ключевых проблем при отсутствии throttle/debounce является накопление устаревших вызовов. Это приводит к:
Throttle решает это через ограничение частоты исполнения, debounce — через обнуление промежуточных вызовов.
В сложных интерфейсах используется комбинация обоих подходов.
Пример: scroll влияет на анимацию (throttle), а изменение layout пересоздаёт её (debounce):
window.addEventListener(
"scroll",
rafThrottle(handleScroll)
);
window.addEventListener(
"resize",
debounce(() => {
destroyAnimation();
initAnimation();
}, 300)
);
Такой подход разделяет потоки:
При работе с анимациями критичны три фактора:
Throttle и debounce выступают инструментами контроля этих факторов, позволяя выстраивать предсказуемые и дешёвые по вычислениям анимационные системы, где Motion One отвечает за интерполяцию и рендер, а внешние утилиты — за поток событий.