Skip to main content

Функція requestAnimationFrame()

requestAnimationFrame(callback), це вбудований API браузера, який планує виконання функції callback перед наступним кадром відмальовування, тобто перед черговим перемальовуванням екрана. Браузер сам вирішує, коли саме настане цей момент, і завдяки цьому анімація йде в такт із частотою оновлення дисплея.

Теорія

TL;DR

  • requestAnimationFrame(fn) викликає fn безпосередньо перед наступним кадром відмальовування.
  • Браузер малює приблизно 60 кадрів на секунду, тобто кадр кожні 16.6 мс, і підлаштовує виклики під реальну частоту екрана.
  • У колбек передається timestamp, час початку кадру в мілісекундах за шкалою performance.now().
  • Виклик повертає ID кадру, який скасовується через cancelAnimationFrame(id).
  • Повторний виклик усередині колбека утворює цикл анімації, кадр за кадром.
  • На неактивній вкладці браузер призупиняє виклики, тому анімація не витрачає батарею й CPU.

Швидкий приклад

javascript
function animate(time) { console.log('Frame at', time, 'ms'); requestAnimationFrame(animate); // request the next frame } requestAnimationFrame(animate);

Що тут відбувається:

  • animate викликається перед кожним кадром відмальовування;
  • time, це timestamp у мілісекундах, який показує, коли саме почалося відмальовування кадру (за performance.now()).

Як це працює під капотом

  1. Ви викликаєте requestAnimationFrame(fn).
  2. Функція fn зберігається браузером у списку колбеків поточного кадру.
  3. Коли браузер готовий малювати новий кадр (зазвичай приблизно через 16 мс), він перед перемальовуванням викликає всі такі функції.
  4. Після виклику колбеків браузер перемальовує екран.

Якщо всередині колбека знову викликати requestAnimationFrame(), анімація піде в циклі, кадр за кадром. Важливо, що це окрема черга візуальних оновлень: вона обробляється перед рендером, а не разом зі звичайними макротасками.

Плавне переміщення елемента

javascript
const box = document.querySelector('.box'); let start = null; function move(timestamp) { if (!start) start = timestamp; const progress = timestamp - start; box.style.transform = `translateX(${progress / 5}px)`; if (progress < 2000) { // 2 seconds requestAnimationFrame(move); } } requestAnimationFrame(move);

Тут елемент плавно рухається протягом двох секунд. Прогрес рахується не від кількості кадрів, а від різниці timestamp, тому на швидкому й на повільному пристрої анімація триватиме однаковий час. Браузер сам оптимізує виклики: якщо вкладка не видима, requestAnimationFrame призупиняється, щоб не витрачати ресурси.

Відмінності від setTimeout і setInterval

КритерійsetTimeout / setIntervalrequestAnimationFrame
ЧастотаФіксована (наприклад, кожні 16 мс)Синхронізується з частотою оновлення екрана
ЕнергоспоживанняНе призупиняються у прихованих вкладкахАвтоматично зупиняється, якщо вкладка прихована
ПлавністьМоже «рватися» під навантаженнямМаксимально плавне відмальовування
ВикористанняТаймери, фонові задачіАнімації, плавні оновлення UI
Передає timestampНіТак, час кадру за performance.now()

Той самий рух, написаний двома способами:

javascript
function moveWithTimeout() { box.style.left = `${parseInt(box.style.left || 0) + 1}px`; setTimeout(moveWithTimeout, 16); } function moveWithRaf() { box.style.left = `${parseInt(box.style.left || 0) + 1}px`; requestAnimationFrame(moveWithRaf); }

setTimeout не враховує навантаження на браузер і може «проскакувати» кадри: 16 мс, це лише мінімальна затримка, а не гарантія. requestAnimationFrame підлаштовується під частоту дисплея, зокрема під 120 Гц, і завжди дає максимально плавний результат.

Скасування кадру

Якщо запланований кадр більше не потрібен, його скасовують за ID:

javascript
const id = requestAnimationFrame(step); cancelAnimationFrame(id);

Це обов'язково робити, коли компонент розмонтовується або анімація зупиняється достроково, інакше цикл продовжить працювати й триматиме посилання на вже непотрібні вузли DOM.

Підсумок

ВластивістьОпис
Що робитьЗапускає колбек перед наступним кадром відмальовування
Коли викликаєтьсяКоли браузер готовий намалювати новий кадр (приблизно 60 FPS)
Аргумент функціїtimestamp, час початку кадру
ПовертаєID кадру (для cancelAnimationFrame)
Зупиняється автоматичноТак, якщо вкладка неактивна
Тип задачіОкрема черга візуальних оновлень, до рендера
Ідеально підходить дляАнімацій, плавних трансформацій, прогрес-барів

Типові помилки

  • Анімувати через setInterval(fn, 16). Таймер не синхронізований з екраном, кадри «пливуть», а на прихованій вкладці робота триває дарма.
  • Рахувати прогрес у кадрах, а не в часі. Кількість кадрів залежить від пристрою, тому анімація на 120 Гц закінчиться вдвічі швидше. Рахуйте від timestamp.
  • Не скасовувати кадр. Без cancelAnimationFrame() цикл продовжує працювати після того, як елемент зник, і тримає його в пам'яті.
  • Читати й писати стилі впереміш. Читання offsetWidth після запису стилю всередині колбека викликає примусовий reflow і вбиває плавність. Спочатку читайте, потім пишіть.
  • Класти в колбек важкі обчислення. Якщо кадр не вклався в 16 мс, браузер його пропустить. Важку роботу виносьте у Worker або розбивайте між кадрами.
  • Чекати на нього поза браузером. Це DOM API, у чистому Node.js його немає, там для схожих задач використовують таймери.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.