Animações suaves e de alto desempenho são essenciais em aplicativos da web modernos. No entanto, gerenciá-los incorretamente pode sobrecarregar o thread principal do navegador, causando baixo desempenho e animações instáveis. requestAnimationFrame (rAF) é uma API de navegador projetada para sincronizar animações com a taxa de atualização da tela, garantindo um movimento mais suave em comparação com alternativas como setTimeout. Mas o uso eficiente do rAF requer um planejamento cuidadoso, especialmente ao lidar com múltiplas animações.
Neste artigo, exploraremos como otimizar requestAnimationFrame centralizando o gerenciamento de animação, introduzindo o controle de FPS e mantendo o thread principal do navegador responsivo.
Quadros por segundo (FPS) são cruciais ao discutir o desempenho da animação. A maioria das telas é atualizada a 60 FPS, o que significa que requestAnimationFrame é chamado 60 vezes por segundo. Para manter animações suaves, o navegador deve concluir seu trabalho em cerca de 16,67 milissegundos por quadro.
Se muitas tarefas forem executadas durante um único quadro, o navegador poderá perder o tempo de quadro alvo, causando interrupções ou queda de quadros. Reduzir o FPS para certas animações pode ajudar a reduzir a carga no thread principal, proporcionando um equilíbrio entre desempenho e suavidade.
Para gerenciar animações com mais eficiência, podemos centralizar seu tratamento com um loop compartilhado em vez de ter várias chamadas requestAnimationFrame espalhadas por todo o código. Uma abordagem centralizada minimiza chamadas redundantes e facilita a adição de controle de FPS.
A classe AnimationManager a seguir nos permite registrar e cancelar o registro de tarefas de animação enquanto controlamos o FPS alvo. Por padrão, nosso objetivo é 60 FPS, mas isso pode ser ajustado de acordo com as necessidades de desempenho.
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();
Nesta configuração, calculamos o deltaTime entre os frames para determinar se passou tempo suficiente para a próxima atualização com base no FPS alvo. Isso nos permite limitar a frequência das atualizações para garantir que o thread principal do navegador não fique sobrecarregado.
Vamos criar um exemplo onde animamos três caixas, cada uma com uma animação diferente: uma escala, outra muda de cor e a terceira gira.
Aqui está o HTML:
Aqui está o CSS:
.animated-box { width: 100px; height: 100px; background-color: #3498db; transition: transform 0.1s ease; }
Agora, adicionaremos JavaScript para animar cada caixa com uma propriedade diferente. Um será dimensionado, outro mudará de cor e o terceiro girará.
Etapa 1: Adicionar interpolação linear (lerp)
Interpolação linear (lerp) é uma técnica comum usada em animações para fazer uma transição suave entre dois valores. Ajuda a criar uma progressão gradual e suave, tornando-o ideal para dimensionar, mover ou alterar propriedades ao longo do tempo. A função leva três parâmetros: um valor inicial, um valor final e um tempo normalizado (t), que determina o quão longe está a transição.
function lerp(start: number, end: number, t: number): number { return start (end - start) * t; }
Etapa 2: dimensionamento da animação
Começamos criando uma função para animar o dimensionamento da primeira caixa:
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); }
Etapa 3: Animação colorida
A seguir, animamos a mudança de cor da segunda caixa:
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); }
Etapa 4: animação de rotação
Finalmente, criamos a função para girar a terceira caixa:
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); }
Etapa 5: Iniciando as animações
Finalmente, podemos iniciar as animações para todas as três caixas:
// 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
Ao usar requestAnimationFrame, é essencial ter em mente que as animações são executadas no thread principal do navegador. Sobrecarregar o thread principal com muitas tarefas pode fazer com que o navegador perca quadros de animação, resultando em travamentos. É por isso que otimizar suas animações com ferramentas como um gerenciador de animação centralizado e controle de FPS pode ajudar a manter a suavidade, mesmo com múltiplas animações.
Gerenciar animações com eficiência em JavaScript requer mais do que apenas usar requestAnimationFrame. Ao centralizar as animações e controlar o FPS, você pode garantir animações mais suaves e com melhor desempenho, mantendo a capacidade de resposta do thread principal. Neste exemplo, mostramos como lidar com múltiplas animações com um único AnimationManager, demonstrando como otimizar o desempenho e a usabilidade. Embora tenhamos nos concentrado em manter um FPS consistente para simplificar, essa abordagem pode ser expandida para lidar com diferentes valores de FPS para diversas animações, embora isso esteja além do escopo deste artigo.
Repositório GitHub: https://github.com/JBassx/rAF-optimization
StackBlitz: https://stackblitz.com/~/github.com/JBassx/rAF-optimization
LinkedIn: https://www.linkedin.com/in/josephciullo/
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3