Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Функція requestAnimationFrame()». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`requestAnimationFrame(callback)`, це вбудований API браузера, який планує виконання `callback` безпосередньо перед наступним кадром відмальовування.** Браузер перемальовує екран приблизно 60 разів на секунду, тобто кожні 16.6 мс, і викликає ваш колбек саме в той момент, коли готується малювати новий кадр, тому анімація виходить максимально плавною. У колбек передається `timestamp`, час початку кадру за шкалою `performance.now()`, а сам виклик повертає ID кадру, який можна передати в `cancelAnimationFrame(id)`. Якщо всередині колбека знову викликати `requestAnimationFrame()`, анімація йтиме циклом, кадр за кадром. Коли вкладка неактивна, браузер сам призупиняє ці виклики й економить ресурси, чого таймери не роблять. ```javascript function animate(time) { box.style.transform = `translateX(${time / 10}px)`; requestAnimationFrame(animate); // ask for the next frame } requestAnimationFrame(animate); ``` **Ключове:** `requestAnimationFrame()` синхронізує ваш код із частотою оновлення екрана, тому це правильний інструмент для анімацій, а не `setTimeout`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`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` / `setInterval` | `requestAnimationFrame` | | --- | --- | --- | | Частота | Фіксована (наприклад, кожні 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 його немає, там для схожих задач використовують таймери.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.