Las animaciones fluidas y eficaces son esenciales en las aplicaciones web modernas. Sin embargo, administrarlos incorrectamente puede sobrecargar el hilo principal del navegador, provocando un rendimiento deficiente y animaciones deficientes. requestAnimationFrame (rAF) es una API del navegador diseñada para sincronizar animaciones con la frecuencia de actualización de la pantalla, lo que garantiza un movimiento más fluido en comparación con alternativas como setTimeout. Pero usar rAF de manera eficiente requiere una planificación cuidadosa, especialmente cuando se manejan múltiples animaciones.
En este artículo, exploraremos cómo optimizar requestAnimationFrame centralizando la administración de animaciones, introduciendo el control de FPS y manteniendo la capacidad de respuesta del hilo principal del navegador.
Los cuadros por segundo (FPS) son cruciales cuando se habla del rendimiento de la animación. La mayoría de las pantallas se actualizan a 60 FPS, lo que significa que se llama a requestAnimationFrame 60 veces por segundo. Para mantener animaciones fluidas, el navegador debe completar su trabajo en aproximadamente 16,67 milisegundos por cuadro.
Si se ejecutan demasiadas tareas durante un solo fotograma, el navegador podría perder el tiempo del fotograma objetivo, lo que provocaría tartamudeo o pérdida de fotogramas. Reducir el FPS para ciertas animaciones puede ayudar a reducir la carga en el hilo principal, proporcionando un equilibrio entre rendimiento y fluidez.
Para administrar las animaciones de manera más eficiente, podemos centralizar su manejo con un bucle compartido en lugar de tener múltiples llamadas requestAnimationFrame dispersas por todo el código. Un enfoque centralizado minimiza las llamadas redundantes y facilita agregar control de FPS.
La siguiente clase AnimationManager nos permite registrar y cancelar el registro de tareas de animación mientras controlamos el FPS objetivo. De forma predeterminada, nuestro objetivo es 60 FPS, pero esto se puede ajustar según las necesidades de rendimiento.
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();
En esta configuración, calculamos el deltaTime entre cuadros para determinar si ha pasado suficiente tiempo para la siguiente actualización según el FPS objetivo. Esto nos permite limitar la frecuencia de las actualizaciones para garantizar que el hilo principal del navegador no esté sobrecargado.
Creemos un ejemplo en el que animamos tres cuadros, cada uno con una animación diferente: uno escala, otro cambia de color y el tercero gira.
Aquí está el HTML:
Aquí está el CSS:
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
Ahora agregaremos JavaScript para animar cada cuadro con una propiedad diferente. Uno escalará, otro cambiará de color y el tercero rotará.
Paso 1: Agregar interpolación lineal (lerp)
La interpolación lineal (lerp) es una técnica común utilizada en animaciones para realizar una transición suave entre dos valores. Ayuda a crear una progresión gradual y suave, lo que lo hace ideal para escalar, mover o cambiar propiedades con el tiempo. La función toma tres parámetros: un valor inicial, un valor final y un tiempo normalizado (t), que determina qué tan avanzada está la transición.
function lerp(start: number, end: number, t: number): number { return start (end - start) * t; }
Paso 2: Escalar la animación
Comenzamos creando una función para animar la escala del primer cuadro:
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); }
Paso 3: Animación en color
A continuación, animamos el cambio de color del segundo cuadro:
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); }
Paso 4: Animación de rotación
Finalmente, creamos la función para rotar el tercer cuadro:
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); }
Paso 5: Iniciar las animaciones
Finalmente, podemos iniciar las animaciones de los tres cuadros:
// 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
Al utilizar requestAnimationFrame, es esencial tener en cuenta que las animaciones se ejecutan en el hilo principal del navegador. Sobrecargar el hilo principal con demasiadas tareas puede hacer que el navegador pierda fotogramas de animación, lo que provoca tartamudeo. Es por eso que optimizar tus animaciones con herramientas como un administrador de animación centralizado y control de FPS puede ayudar a mantener la fluidez, incluso con múltiples animaciones.
Administrar animaciones de manera eficiente en JavaScript requiere algo más que simplemente usar requestAnimationFrame. Al centralizar las animaciones y controlar el FPS, puede garantizar animaciones más fluidas y con mayor rendimiento mientras mantiene la capacidad de respuesta del hilo principal. En este ejemplo, mostramos cómo manejar múltiples animaciones con un solo AnimationManager, demostrando cómo optimizar tanto el rendimiento como la usabilidad. Si bien nos concentramos en mantener un FPS constante para simplificar, este enfoque se puede ampliar para manejar diferentes valores de FPS para varias animaciones, aunque eso estaba más allá del alcance de este artículo.
Repositorio de Github: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3