Плавная и производительная анимация необходима в современных веб-приложениях. Однако неправильное управление ими может привести к перегрузке основного потока браузера, что приведет к снижению производительности и прерывистой анимации. requestAnimationFrame (rAF) — это API браузера, предназначенный для синхронизации анимации с частотой обновления дисплея, обеспечивая более плавное движение по сравнению с альтернативами, такими как setTimeout. Но эффективное использование rAF требует тщательного планирования, особенно при работе с несколькими анимациями.
В этой статье мы рассмотрим, как оптимизировать requestAnimationFrame путем централизации управления анимацией, введения контроля FPS и поддержания отзывчивости основного потока браузера.
Количество кадров в секунду (FPS) имеет решающее значение при обсуждении производительности анимации. Большинство экранов обновляются со скоростью 60 кадров в секунду, что означает, что requestAnimationFrame вызывается 60 раз в секунду. Чтобы поддерживать плавную анимацию, браузер должен выполнять свою работу примерно за 16,67 миллисекунды на кадр.
Если в одном кадре выполняется слишком много задач, браузер может пропустить целевое время кадра, что приведет к зависанию или пропуску кадров. Понижение FPS для определенных анимаций может помочь снизить нагрузку на основной поток, обеспечивая баланс между производительностью и плавностью.
Чтобы управлять анимацией более эффективно, мы можем централизовать ее обработку с помощью общего цикла, а не использовать несколько вызовов requestAnimationFrame, разбросанных по всему коду. Централизованный подход сводит к минимуму избыточные вызовы и упрощает добавление контроля FPS.
Следующий класс AnimationManager позволяет нам регистрировать и отменять регистрацию задач анимации, одновременно контролируя целевой FPS. По умолчанию мы стремимся к 60 кадрам в секунду, но это можно настроить в зависимости от производительности.
class AnimationManager { private tasks: Set= new Set(); private fps: number = 60; // Target FPS private lastFrameTime: number = performance.now(); private animationId: number | null = null; // Store the animation frame ID private run = (currentTime: number) => { const deltaTime = currentTime - this.lastFrameTime; // Ensure the tasks only run if enough time has passed to meet the target FPS if (deltaTime > 1000 / this.fps) { this.tasks.forEach((task) => task(currentTime)); this.lastFrameTime = currentTime; } this.animationId = requestAnimationFrame(this.run); }; public registerTask(task: FrameRequestCallback) { this.tasks.add(task); if (this.tasks.size === 1) { this.animationId = requestAnimationFrame(this.run); // Start the loop if this is the first task } } public unregisterTask(task: FrameRequestCallback) { this.tasks.delete(task); if (this.tasks.size === 0 && this.animationId !== null) { cancelAnimationFrame(this.animationId); // Stop the loop if no tasks remain this.animationId = null; // Reset the ID } } } export const animationManager = new AnimationManager();
В этой настройке мы вычисляем deltaTime между кадрами, чтобы определить, прошло ли достаточно времени для следующего обновления на основе целевого FPS. Это позволяет нам регулировать частоту обновлений, чтобы основной поток браузера не был перегружен.
Давайте создадим пример, в котором мы анимируем три блока, каждый из которых имеет разную анимацию: один масштабируется, другой меняет цвет, а третий вращается.
Вот HTML:
Вот CSS:
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
Теперь мы добавим JavaScript для анимации каждого поля с различным свойством. Один будет масштабироваться, другой менять цвет, а третий вращаться.
Шаг 1. Добавление линейной интерполяции (lerp)
Линейная интерполяция (lerp) — это распространенный метод, используемый в анимации для плавного перехода между двумя значениями. Это помогает создать постепенное и плавное развитие, что делает его идеальным для масштабирования, перемещения или изменения свойств с течением времени. Функция принимает три параметра: начальное значение, конечное значение и нормализованное время (t), которое определяет глубину перехода.
function lerp(start: number, end: number, t: number): number { return start (end - start) * t; }
Шаг 2. Масштабирование анимации
Начнем с создания функции для анимации масштабирования первого блока:
function animateScale( scaleBox: HTMLDivElement, startScale: number, endScale: number, speed: number ) { let scaleT = 0; function scale() { scaleT = speed; if (scaleT > 1) scaleT = 1; const currentScale = lerp(startScale, endScale, scaleT); scaleBox.style.transform = `scale(${currentScale})`; if (scaleT === 1) { animationManager.unregisterTask(scale); } } animationManager.registerTask(scale); }
Шаг 3. Цветовая анимация
Далее анимируем изменение цвета второго прямоугольника:
function animateColor( colorBox: HTMLDivElement, startColor: number, endColor: number, speed: number ) { let colorT = 0; function color() { colorT = speed; if (colorT > 1) colorT = 1; const currentColor = Math.floor(lerp(startColor, endColor, colorT)); colorBox.style.backgroundColor = `rgb(${currentColor}, 100, 100)`; if (colorT === 1) { animationManager.unregisterTask(color); } } animationManager.registerTask(color); }
Шаг 4. Анимация вращения
Наконец, мы создаем функцию для поворота третьего блока:
function animateRotation( rotateBox: HTMLDivElement, startRotation: number, endRotation: number, speed: number ) { let rotationT = 0; function rotate() { rotationT = speed; // Increment progress if (rotationT > 1) rotationT = 1; const currentRotation = lerp(startRotation, endRotation, rotationT); rotateBox.style.transform = `rotate(${currentRotation}deg)`; // Unregister task once the animation completes if (rotationT === 1) { animationManager.unregisterTask(rotate); } } animationManager.registerTask(rotate); }
Шаг 5. Запуск анимации
Наконец, мы можем запустить анимацию для всех трех ящиков:
// Selecting the elements const scaleBox = document.querySelector("#animate-box-1") as HTMLDivElement; const colorBox = document.querySelector("#animate-box-2") as HTMLDivElement; const rotateBox = document.querySelector("#animate-box-3") as HTMLDivElement; // Starting the animations animateScale(scaleBox, 1, 1.5, 0.02); // Scaling animation animateColor(colorBox, 0, 255, 0.01); // Color change animation animateRotation(rotateBox, 360, 1, 0.005); // Rotation animation
При использовании requestAnimationFrame важно помнить, что анимация выполняется в основном потоке браузера. Перегрузка основного потока слишком большим количеством задач может привести к тому, что браузер пропустит кадры анимации, что приведет к зависаниям. Вот почему оптимизация анимации с помощью таких инструментов, как централизованный менеджер анимации и контроль частоты кадров, может помочь сохранить плавность даже при использовании нескольких анимаций.
Эффективное управление анимацией в JavaScript требует большего, чем просто использование requestAnimationFrame. Централизуя анимацию и контролируя частоту кадров, вы можете обеспечить более плавную и производительную анимацию, сохраняя при этом отзывчивость основного потока. В этом примере мы показали, как обрабатывать несколько анимаций с помощью одного AnimationManager, демонстрируя, как оптимизировать производительность и удобство использования. Хотя для простоты мы сосредоточились на поддержании постоянного значения FPS, этот подход можно расширить для обработки разных значений FPS для различных анимаций, хотя это выходит за рамки этой статьи.
Репозиторий Github: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3