В чем разница между requestAnimationFrame и setTimeout в стеке задач?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В чем разница между requestAnimationFrame и setTimeout в стеке задач?
requestAnimationFrame (rAF) и setTimeout — это два разных способа планирования выполнения кода в JavaScript, и они работают в разных местах системы обработки событий браузера.
Event Loop и стек задач
Сначала разберемся со структурой обработки событий:
1. Call Stack (основной стек - синхронный код)
2. Web APIs (таймеры, события и т.д.)
3. Callback Queue / Macrotask Queue (задачи)
4. Microtask Queue (микротадачи)
5. Rendering (рендеринг)
setTimeout в Event Loop
setTimeout создает задачу в Macrotask Queue (также называется Task Queue):
console.log('1. Синхронный код');
setTimeout(() => {
console.log('3. Из setTimeout');
}, 0);
console.log('2. Еще синхронный код');
// Вывод:
// 1. Синхронный код
// 2. Еще синхронный код
// 3. Из setTimeout (выполнится после синхронного кода)
requestAnimationFrame в Event Loop
requestAnimationFrame встраивается в цикл рендеринга браузера, выполняясь перед рендерингом, но после microtak queue:
console.log('1. Синхронный код');
requestAnimationFrame(() => {
console.log('3. Из requestAnimationFrame');
});
console.log('2. Еще синхронный код');
// Вывод:
// 1. Синхронный код
// 2. Еще синхронный код
// 3. Из requestAnimationFrame (выполнится перед рендерингом)
Порядок выполнения в Event Loop
Вот полный порядок выполнения в каждой итерации Event Loop:
1. Выполни весь код в Call Stack (синхронный)
2. Выполни всю Microtask Queue (Promise, MutationObserver)
3. Выполни ДНА одну задачу из Macrotask Queue (setTimeout, setInterval)
4. Выполни все новые microtask (которые появились)
5. Выполни рендеринг (если нужен)
6. Выполни requestAnimationFrame
7. Повтори
Пример: setTimeout vs requestAnimationFrame
console.log('START');
setTimeout(() => {
console.log('setTimeout 1');
}, 0);
Promise.resolve().then(() => {
console.log('Promise');
});
requestAnimationFrame(() => {
console.log('requestAnimationFrame 1');
});
console.log('END');
// Вывод:
// START
// END
// Promise (microtask)
// requestAnimationFrame 1 (перед рендерингом)
// setTimeout 1 (после рендеринга)
Синхронизация с рендерингом
setTimeout не синхронизирован с рендерингом:
// Может выполниться в любой момент, не обязательно перед рендерингом
setTimeout(() => {
element.style.left = '100px'; // Может быть слишком поздно
}, 0);
requestAnimationFrame синхронизирован и выполняется ДО рендеринга:
// Гарантированно выполнится перед рендерингом
requestAnimationFrame(() => {
element.style.left = '100px'; // Будет отрисовано в одном frame
});
Частота вызовов
setTimeout:
// Выполняется каждые 16ms (примерно, минимум 4ms)
setInterval(() => {
console.log('tick');
}, 16);
// На 60 FPS экране может быть нестабильно
requestAnimationFrame:
// Выполняется синхронно с частотой экрана (60 FPS = 16.67ms)
function animate() {
console.log('frame');
requestAnimationFrame(animate);
}
animate();
// На всех устройствах синхронизировано с дисплеем
Производительность
setTimeout может привести к "jank" (дергаанию):
// Плохо: может прервать рендеринг
function animate() {
element.style.left = x + 'px';
setTimeout(animate, 16);
}
// Плохо: выполняется вне цикла рендеринга
const startTime = Date.now();
function moveElement() {
const elapsed = Date.now() - startTime;
element.style.left = (elapsed * 0.1) + 'px';
if (elapsed < 1000) {
setTimeout(moveElement, 16);
}
}
moveElement();
requestAnimationFrame оптимизирован для анимаций:
// Хорошо: синхронизировано с рендерингом
function animate() {
element.style.left = x + 'px';
requestAnimationFrame(animate);
}
// Хорошо: выполняется в правильном месте Event Loop
const startTime = performance.now();
function moveElement(currentTime) {
const elapsed = currentTime - startTime;
element.style.left = (elapsed * 0.1) + 'px';
if (elapsed < 1000) {
requestAnimationFrame(moveElement);
}
}
requestAnimationFrame(moveElement);
Практические различия
Табличка сравнения
| Характеристика | setTimeout | requestAnimationFrame |
|---|---|---|
| Queue | Macrotask | Rendering Pipeline |
| Частота | Зависит от параметра | Синхронна с экраном |
| Минимальный интервал | 4ms | 16.67ms (60 FPS) |
| Синхронизация рендеринга | Нет | Да |
| Лучше для анимаций | Нет | Да |
| Лучше для работы с DOM | Нет | Да |
| Можно отменить | clearTimeout | cancelAnimationFrame |
Примеры использования
1. Анимация - используй requestAnimationFrame
class Animator {
constructor(element) {
this.element = element;
this.position = 0;
this.animationId = null;
}
animate() {
this.position += 1;
this.element.style.left = this.position + 'px';
if (this.position < 100) {
this.animationId = requestAnimationFrame(() => this.animate());
}
}
stop() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
2. Отложенное выполнение - используй setTimeout
// Отправляем данные с задержкой
function sendAnalytics(data) {
setTimeout(() => {
fetch('/api/analytics', { method: 'POST', body: JSON.stringify(data) });
}, 5000);
}
// Обработка потенциально дорогой операции
function processLargeData(data) {
setTimeout(() => {
// Позволяет браузеру отрисовать UI перед обработкой
expensiveCalculation(data);
}, 0);
}
3. Мониторинг производительности
// requestAnimationFrame показывает реальную частоту кадров
let frameCount = 0;
let lastTime = performance.now();
function measureFPS() {
frameCount++;
const now = performance.now();
if (now >= lastTime + 1000) {
console.log('FPS:', frameCount);
frameCount = 0;
lastTime = now;
}
requestAnimationFrame(measureFPS);
}
measureFPS();
4. Плавное обновление UI при скролле
// Плохо: jank во время скролла
window.addEventListener('scroll', () => {
updateUIExpensively(); // Блокирует рендеринг
});
// Хорошо: не блокирует рендеринг
let isScheduled = false;
window.addEventListener('scroll', () => {
if (!isScheduled) {
requestAnimationFrame(() => {
updateUIExpensively();
isScheduled = false;
});
isScheduled = true;
}
}, { passive: true });
Современный подход с async/await
// Использование setTimeout с Promise
async function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Использование requestAnimationFrame с Promise
function nextFrame() {
return new Promise(resolve => requestAnimationFrame(resolve));
}
// Использование
async function animate() {
for (let i = 0; i < 100; i++) {
element.style.left = i + 'px';
await nextFrame(); // Ждем следующего кадра
}
}
Резюме
setTimeout:
- Находится в Macrotask Queue
- Выполняется ПОСЛЕ рендеринга
- Может привести к jank в анимациях
- Используй для отложенных операций
requestAnimationFrame:
- Встроена в цикл рендеринга
- Выполняется ПЕРЕД рендерингом
- Синхронизирована с экраном (60 FPS)
- Используй для анимаций и обновлений DOM
Главное правило: для анимаций и частых обновлений DOM используй requestAnimationFrame, для остального используй setTimeout.