Кожна симуляція на цьому сайті використовує цикл на основі
requestAnimationFrame (rAF). Причини виходять далеко за
межі «це сучасний спосіб» — існують конкретні, вимірювані
відмінності, які впливають на коректність, продуктивність та
користувацький досвід.
Що насправді не так із setInterval?
setInterval(fn, 16) спрацьовує кожні 16 мс незалежно
від того, чи готовий браузер до рендерингу. Це створює три
проблеми:
- Не синхронізовано з vsync. Компоузер браузера малює з частотою 60/90/120 Гц (залежно від можливостей дисплея). setInterval спрацьовує незалежно, викликаючи розриви кадру (tearing) та марну роботу, коли оновлення відбуваються між циклами малювання.
- Працює у прихованих вкладках. Коли користувач перемикається на іншу вкладку, setInterval продовжує спрацьовувати — споживаючи процесор, розряджаючи батарею та накопичуючи час симуляції, який виливається одразу, коли вкладка знову стає видимою.
- Дрейф таймера. Таймери JavaScript не працюють у режимі реального часу. Їх можуть затримувати паузи збирача сміття, робота з версткою або затримки в обробниках подій вводу. Інтервал у 16 мс на практиці може спрацьовувати на 16, 17, 14, 22 мс.
Що дає requestAnimationFrame
Колбеки rAF отримують параметр DOMHighResTimeStamp із
точністю до частки мілісекунди (як правило). Колбек спрацьовує з
частотою оновлення дисплея — браузер планує його якраз перед
наступним малюванням, синхронно з компоузером.
- Автоматичне тротлінг. Коли вкладка йде у фон, rAF автоматично призупиняється. У деяких браузерах частота падає приблизно до 1 кадру на секунду, щоб таймери залишалися живими, але суттєвого навантаження на процесор це не створює.
- Вирівнювання за vsync. Колбек спрацьовує у потрібний момент конвеєра рендерингу браузера: після верстки, перед композицією. Зміни всередині rAF не змушують виконувати зайву верстку.
-
Точний дельта-час. Оскільки надається
мітка часу, можна обчислити
dt = t - prevTі множити на неї всі оновлення фізики. Це робить фізику незалежною від частоти кадрів.
Правильний патерн
let prevTime = performance.now();
function loop(time) {
const dt = Math.min((time - prevTime) / 1000, 0.05); // секунди, обмежено 50 мс
prevTime = time;
update(dt); // крок фізики — множимо прискорення на dt, швидкості на dt
render(); // малюємо кадр
requestAnimationFrame(loop);
}
requestAnimationFrame(loop); // старт
Обмеження Math.min(..., 0.05) важливе: якщо вкладка
була прихована, а потім знову стала активною, dt
міг би дорівнювати кільком секундам, через що об'єкти
«телепортувалися» б. Обмеження встановлює максимальний крок фізики
на рівні 50 мс, щоб короткий лаг не «підірвав» симуляцію.
Фіксований крок часу (для детермінованої фізики)
Для стабільної фізики (виявлення зіткнень, динаміка пружин)
змінний dt може призводити до нестабільності при
низькій частоті кадрів. Використовуйте акумулятор із фіксованим
кроком часу:
const FIXED_DT = 1 / 60; // фізика на 60 Гц
let accumulator = 0;
function loop(time) {
const frameTime = Math.min((time - prevTime) / 1000, 0.1);
prevTime = time;
accumulator += frameTime;
while (accumulator >= FIXED_DT) {
physicsStep(FIXED_DT);
accumulator -= FIXED_DT;
}
render();
requestAnimationFrame(loop);
}
Це виконує стільки кроків фізики, скільки потрібно, щоб «наздогнати»
реальний час, при цьому кожен крок завжди дорівнює рівно
1/60 с. Рендеринг може відбуватися між кроками —
використовуйте залишкову частку
accumulator / FIXED_DT для інтерполяції стану
рендерингу заради плавної картинки навіть при 120 Гц.
✅ requestAnimationFrame
- Вирівняно за vsync, без розривів кадру
- Автоматично призупиняється у прихованих вкладках
- Точна мітка часу високої роздільної здатності
- Планування, оптимізоване браузером
- Менше навантаження на процесор у фоні
❌ setInterval
- Не вирівняно за vsync
- Працює у прихованих вкладках (розряджає батарею)
- Дрейф таймера під навантаженням
- Немає вбудованого дельта-часу
- Може викликати «спіраль смерті» надолуження
Правило: Ніколи не використовуйте
setInterval для анімації. Використовуйте
requestAnimationFrame з акумулятором дельта-часу для
рендерингу зі змінною частотою та додавайте внутрішній цикл із
фіксованим кроком часу для стабільності фізики.