Керування fps за допомогою requestAnimationFrame?

Здається, requestAnimationFrame - це фактичний спосіб анімувати речі зараз. Здебільшого це спрацювало у мене досить добре, але зараз я намагаюся зробити кілька анімаційних полотен, і мені було цікаво: чи є спосіб переконатися, що він працює з певною частотою кадрів в секунду? Я розумію, що метою rAF є стабільно плавна анімація, і я міг би ризикнути зробити свою анімацію непосильною, але зараз, схоже, вона працює на різно різній швидкості досить довільно, і мені цікаво, чи є спосіб боротьби це якось.

Я б використовував setInterval, але я хочу оптимізації, яку пропонує rAF (особливо автоматично зупиняючись, коли вкладка знаходиться у фокусі).

Якщо хтось захоче поглянути на мій код, це в значній мірі:

Де Node.drawFlash () - це лише якийсь код, який визначає радіус на основі змінної лічильника, а потім малює коло.

10 відповідей 10

Як придушити requestAnimationFrame до певної частоти кадрів

Цей метод працює, перевіряючи час, що минув з моменту виконання останнього циклу кадру.

Код креслення виконується лише тоді, коли вказаний інтервал FPS минув.

Перша частина коду встановлює деякі змінні, що використовуються для обчислення минулого часу.

І цей код є фактичним циклом requestAnimationFrame, який звертається до вказаного вами FPS.

javascript

Оновлення 2016/6

Проблема зменшення частоти кадрів полягає в тому, що екран має постійну швидкість оновлення, як правило, 60 кадрів в секунду.

Якщо ми хочемо 24 кадрів в секунду, ми ніколи не отримаємо справжніх 24 кадрів в секунду на екрані, ми можемо визначити час як такий, але не показувати його, оскільки монітор може показувати синхронізовані кадри лише з частотою 15 кадрів в секунду, 30 кадрів в секунду або 60 кадрів в секунду (деякі монітори також 120 кадрів в секунду) ).

Однак для цілей хронометражу ми можемо підрахувати та оновити, коли це можливо.

Ви можете побудувати всю логіку для управління частотою кадрів, інкапсулюючи обчислення та зворотні виклики в об'єкт:

Потім додайте контролер та код конфігурації:

Використання

Це стає дуже просто - тепер все, що нам потрібно зробити, це створити екземпляр, встановивши функцію зворотного виклику та бажану частоту кадрів приблизно так:

Потім запустіть (що за бажанням може бути поведінкою за замовчуванням):

Ось і все, вся логіка обробляється внутрішньо.

Стара відповідь

Основною метою requestAnimationFrame є синхронізація оновлень із частотою оновлення монітора. Це вимагатиме анімації на FPS монітора або коефіцієнт його (тобто 60, 30, 15 FPS для типової частоти оновлення @ 60 Гц).

Якщо ви хочете отримати більш довільний кадр в секунду, тоді немає сенсу використовувати rAF, оскільки частота кадрів у будь-якому випадку ніколи не буде відповідати частоті оновлення монітора (просто кадр тут і там), що просто не може дати вам плавну анімацію (як і у всіх повторних синхронізаціях кадрів) ), і ви можете замість цього використовувати setTimeout або setInterval.

Це також добре відома проблема у професійній відеоіндустрії, коли ви хочете відтворити відео на інший кадр в секунду, а потім на пристрої, який показує його оновлення. Було використано багато методів, таких як змішування кадрів та складне відновлення синхронізації, відновлення проміжних кадрів на основі векторів руху, але в полотні ці методи недоступні, і результат завжди буде різким відео.

Причиною того, чому ми ставимо setTimeout спочатку (і чому деякі спочатку ставлять rAF, коли використовується полізаливка), є те, що це буде точніше, оскільки setTimeout поставить у чергу подію одразу при запуску циклу, так що незалежно від того, скільки часу залишиться код буде використовувати (за умови, що він не перевищує інтервал очікування) наступний виклик буде на інтервалі, який він представляє (для чистого rAF це не є важливим, оскільки rAF в будь-якому випадку намагатиметься перейти до наступного кадру).

Також варто зазначити, що розміщення його першим також призведе до ризику накопичення викликів, як і у випадку setInterval. setInterval може бути дещо точнішим для цього використання.

І ви можете використовувати setInterval замість того, щоб поза циклом зробити те саме.

І щоб зупинити цикл:

Щоб зменшити частоту кадрів, коли вкладка стає розмитою, ви можете додати такий фактор:

Таким чином ви можете зменшити FPS до 1/4 тощо.

Я пропоную обернути ваш дзвінок requestAnimationFrame в setTimeout:

Вам потрібно викликати requestAnimationFrame зсередини setTimeout, а не навпаки, оскільки requestAnimationFrame планує запуск вашої функції безпосередньо перед наступним перефарбуванням, і якщо ви відкладете оновлення додатково за допомогою setTimeout, ви пропустите це часове вікно. Однак робити зворотне є здоровим, оскільки ви просто чекаєте певний проміжок часу, перш ніж зробити запит.

Це все хороші ідеї в теорії, поки ви не заглибитесь. Проблема полягає в тому, що ви не можете придушити RAF, не десинхронізуючи його, перемінивши його цільову ціль для існуючих. Тож ви дозволяєте йому працювати на повній швидкості та оновлюєте свої дані в окремому циклі, або навіть окрема нитка!

Так, я це сказав. Ви можете зробити багатопотоковий JavaScript у браузері!

Я знаю два методи, які надзвичайно добре працюють без пробки, використовуючи набагато менше соку і створюючи менше тепла. Точний людський масштаб та ефективність роботи машини є результатом.

Вибачення, якщо це трохи багатослівно, але тут йдеться.

Спосіб 1. Оновлення даних через setInterval та графіки через RAF.

Використовуйте окремий setInterval для оновлення значень перекладу та обертання, фізики, зіткнень тощо. Зберігайте ці значення в об’єкті для кожного анімованого елемента. Призначте рядок перетворення змінній в об'єкті кожного 'frame' setInterval. Зберігайте ці об’єкти в масиві. Встановіть бажаний інтервал кадрів в секунду в мс: ms = (1000/fps). Це підтримує стабільний годинник, який дозволяє отримувати однакові fps в будь-якому пристрої, незалежно від швидкості RAF. Не призначайте перетворення елементам тут!

У циклі requestAnimationFrame перегляньте свій масив за допомогою циклу old-school for - не використовуйте тут новіші форми, вони повільні!

У вашій функції rafUpdate отримайте рядок перетворення з вашого js-об'єкта в масиві та ідентифікатора його елементів. Ви вже повинні мати свої елементи “спрайт”, приєднані до змінної або легко доступні за допомогою інших засобів, щоб ви не втрачали час, “отримуючи” їх у RAF. Зберігання їх в об’єкті, названому на честь їх ідентифікатора html, працює досить добре. Налаштуйте цю частину до того, як вона навіть потрапить у ваш SI або RAF.

Використовуйте RAF для оновлення лише ваших перетворень, використовуйте лише 3D-перетворення (навіть для 2d) і встановіть css "will-change: transform;" на елементи, які будуть змінюватися. Це максимально синхронізує ваші перетворення з рідною частотою оновлення, запускає графічний процесор і повідомляє браузеру, де найбільше зосередитись.

Отже, у вас має бути щось на зразок цього псевдокоду.

Це зберігає ваші оновлення об’єктів даних та рядки перетворень синхронізованими з бажаною частотою кадрів у SI, а фактичні призначення перетворення в RAF синхронізуються із частотою оновлення графічного процесора. Отже, фактичні оновлення графіки є лише в RAF, але зміни даних і побудова рядка перетворення знаходяться в SI, отже, ніякі несанкціоновані файли, але "час" не протікає з бажаною частотою кадрів.

Спосіб 2. Помістіть SI у веб-працівника. Цей швидкий і плавний!

Те саме, що і метод 1, але додайте SI до веб-працівника. Тоді він буде працювати в абсолютно окремому потоці, залишаючи сторінку мати справу лише з RAF та UI. Передайте масив спрайтів туди-сюди як "передавальний об'єкт". Це буко швидко. Для клонування чи серіалізації не потрібно часу, але це не те, що передавати посилання, оскільки посилання з іншої сторони знищується, тому вам потрібно буде передати обидві сторони на іншу сторону та оновлювати їх лише за наявності, сортувати подібно передачі записки туди-сюди зі своєю дівчиною в середній школі.

Одночасно вміє читати і писати лише один. Це нормально, поки вони перевіряють, чи не визначено це, щоб уникнути помилки. RAF є ШВИДКИМ і негайно відкине його назад, а потім пройде через купу кадрів графічного процесора, просто перевіряючи, чи він ще не відправлений назад. SI у веб-співробітнику буде мати масив спрайтів більшу частину часу, оновлюватиме позиційні дані, дані руху та фізики, а також створюватиме новий рядок перетворення, а потім передаватиме його назад у RAF на сторінці.

Це найшвидший спосіб, як я знаю, анімувати елементи за допомогою сценарію. Ці дві функції будуть виконуватися як дві окремі програми, у двох окремих потоках, використовуючи переваги багатоядерних процесорів таким чином, як це не робить один скрипт js. Багатопотокова анімація JavaScript.

І це буде робити плавно, без збитків, але з фактично вказаною частотою кадрів, з дуже невеликими розбіжностями.

Будь-який із цих двох методів забезпечить, щоб ваш сценарій працював з однаковою швидкістю на будь-якому ПК, телефоні, планшеті тощо (звичайно, в межах можливостей пристрою та браузера).