CSS vs Animação JS: qual é o mais rápido?
Como pode a animação baseada em JavaScript ser secretamente sempre tão rápida – ou mais rápida – do que as transições CSS? E como é possível que Adobe e Google lancem, consistentemente, sites mobile com rich-media que rivalizam com o desempenho dos apps nativos?
Este artigo serve como um passo a passo de como as bibliotecas de animação DOM baseadas em JavaScript, como Velocity.js e GSAP, são mais eficazes do que jQuery e bibliotecas de animação baseadas em CSS.
jQuery
Vamos começar com o básico: JavaScript e jQuery são falsamente confundidos. A animação de JavaScript é rápida. A do jQuery é mais lenta. Por quê? Porque nunca fez parte dos objetivos de design do jQuery ser um motor performático para animação:
- O jQuery não pode evitar o layout thrashing devido à sua base de código, que serve a muitos propósitos além de animação.
- O consumo de memória do jQuery desencadeia frequentemente coletas de lixo que, momentaneamente, congelam as animações.
- jQuery usa setInterval em vez de requestAnimationFrame (RAF), a fim de proteger novatos de si mesmos.
Note-se que layout thrashing é o que causa travamento (stuttering) no início das animações, a coleta de lixo é o que causa stuttering durante as animações, e a ausência de RAF é o que geralmente produz baixa taxas de frames.
Exemplos de implementação
Evitar o layout thrashing consiste simplesmente em juntar as consultas e as atualizações DOM:
var currentTop, currentLeft; /* With layout thrashing. */ currentTop = element.style.top; /* QUERY */ element.style.top = currentTop + 1; /* UPDATE */ currentLeft = element.style.left; /* QUERY */ element.style.left = currentLeft + 1; /* UPDATE */ /* Without layout thrashing. */ currentTop = element.style.top; /* QUERY */ currentLeft = element.style.left; /* QUERY */ element.style.top = currentTop + 1; /* UPDATE */ element.style.left = currentLeft + 1; /* UPDATE */
As consultas que ocorrem após uma atualização forçam o navegador a recalcular os dados de estilo computadorizado da página (enquanto levam efeitos da nova atualização em consideração). Isso produz uma sobrecarga significativa para animações que estão sendo executadas ao longo de pequenos intervalos de apenas 16ms.
Da mesma forma, implementar RAF não precisa de uma reformulação significativa de sua base de código existente. Vamos comparar a implementação básica de RAF contra a de setInterval:
var startingTop = 0; /* setInterval: Runs every 16ms to achieve 60fps (1000ms/60 ~= 16ms). */ setInterval(function() { /* Since this ticks 60 times a second, we divide the top property's increment of 1 unit per 1 second by 60. */ element.style.top = (startingTop += 1/60); }, 16); /* requestAnimationFrame: Attempts to run at 60fps based on whether the browser is in an optimal state. */ function tick () { element.style.top = (startingTop += 1/60); } window.requestAnimationFrame(tick);
RAF produz o maior boost possível para o desempenho de animação que você pode fazer com uma única alteração em seu código.
Transições CSS
As transições CSS superam jQuery ao descarregar a lógica de animação para o próprio navegador, que é eficiente em 1) otimizar a interação DOM e o consumo de memória para evitar stuttering, 2) alavancar os princípios de RAF nos bastidores e 3) forçar a aceleração de hardware (aproveitando o poder da GPU para melhorar o desempenho da animação).
A realidade, contudo, é que essas otimizações também podem ser realizadas diretamente no JavaScript. GSAP vem fazendo isso há anos. Velocity.js, um novo mecanismo de animação, não só alavanca essas mesmas técnicas, mas também vai vários passos além – como veremos em breve.
Chegar a um acordo com o fato de que a animação JavaScript pode rivalizar com bibliotecas de animação CSS é só o primeiro passo em nosso programa de reabilitação. O segundo passo é perceber que a animação JavaScript pode realmente ser mais rápida do que eles.
Vamos começar analisando os pontos fracos das bibliotecas CSS de animação:
- As transições forçam as taxas de aceleração do hardware do GPU, resultando em stuttering e banding em situações de alto estresse. Esses efeitos são agravados em dispositivos móveis (em específico, o sttutering é um resultado da sobrecarga que ocorre quando os dados são transferidos entre a thread principal do navegador e a thread do compositor. Algumas propriedades CSS, como transforms e opacidade, são imunes a essa sobrecarga). A Adobe fala sobre esse assunto aqui.
- As transições não funcionam para versões abaixo do Internet Explorer 10, causando problemas de acessibilidade para sites de desktop, já que IE8 e IE9 continuam sendo muito populares.
- Pelo fato de as transições não serem nativamente controladas pelo JavaScript (elas só são desencadeadas por JavaScript), o navegador não sabe como otimizar em sincronia as transições com o código JavaScript que as manipula.
Em contrapartida, as bibliotecas de animação baseadas em JavaScript podem decidir por si mesmas quando permitir a aceleração de hardware, elas funcionam inerentemente em todas as versões do IE, e são perfeitamente adequadas para otimizações de animação em lote.
Minha recomendação é usar as transições brutas de CSS quando você estiver desenvolvendo exclusivamente para celular e suas animações forem compostas exclusivamente de mudanças de estado simples. Em tais circunstâncias, as transições são uma solução nativa de alto desempenho e que lhe permitem manter toda a lógica de animação dentro de suas folhas de estilo e evitar inchaço da sua página com bibliotecas JavaScript. Entretanto, se você estiver projetando uma interface de usuário complexas, cheia de floreios, ou se estiver desenvolvendo uma aplicação com informações de estado, sempre utilize uma biblioteca de animação para que as suas animações continuem com bom desempenho e seu fluxo de trabalho permaneça controlável. A Transit é uma biblioteca que faz um trabalho fantástico na gestão das transições brutas de CSS.
Animação em JavaScript
Ok, então o JavaScript pode levar vantagem quando se trata de desempenho. Mas quão mais rápido exatamente ele pode JavaScript ser? Bem, para começar, rápido o suficiente para construir uma intensa demonstração de animação em 3D que você normalmente só vê construída com WebGL. E rápido o suficiente para construir um teaser multimídia que você costuma ver construído com Flash ou After Effects. E também rápido o suficiente para construir um mundo virtual que você normalmente só ver construído com canvas.
Para comparar diretamente o desempenho das principais bibliotecas de animação, incluindo Transit (que usa transições CSS), tenha em mente a documentação do Velocity em VelocityJS.org.
A dúvida continua: como exatamente o JavaScript atinge seus altos níveis de desempenho? Abaixo está uma pequena lista do que as otimizações baseadas em animação JavaScript são capazes de realizar:
- Sincronizar o DOM entre a pilha de toda a cadeia de animação para minimizar o layout thrashing.
- Fazer cache nos valores de propriedade entre as chamadas encadeadas, a fim de minimizar a ocorrência de consulta de DOM (que é o calcanhar de Aquiles do desempenho de animação DOM).
- Fazer cache de taxas de conversão de unidades (por exemplo, px a%, em, etc.) por meio de elementos irmãos na mesma chamada.
- Ignorar a atualização do estilo quando as atualizações seriam imperceptíveis visualmente.
Revendo o que aprendemos anteriormente sobre o layout thrashing, o Velocity.js aproveita essas melhores práticas para armazenar em cache os valores finais de uma animação para serem reutilizados como os valores de início da animação subsequente, evitando assim fazer um novo query do DOM para os valores iniciais do elemento:
$element /* Slide the element down into view. */ .velocity({ opacity: 1, top: "50%" }) /* After a delay of 1000ms, slide the element out of view. */ .velocity({ opacity: 0, top: "-50%" }, { delay: 1000 });
No exemplo acima, a segunda chamada do Velocity sabe que deve iniciar automaticamente com um valor de opacidade de 1 e um valor superior a 50%.
O navegador, em última instância, poderia realizar ele mesmo muitas dessas otimizações, mas fazer isso implicaria agressivamente em reduzir as maneiras pelas quais o código de animação poderia ser trabalhado pelo desenvolvedor. Assim, pela mesma razão que jQuery não usa RAF (veja acima), os navegadores nunca impõem otimizações que têm uma pequena chance de quebrar a especificação ou de se desviar do comportamento esperado.
Finalmente, vamos comparar as duas bibliotecas de animação JavaScript (Velocity.js e GSAP) uma contra a outra.
- GSAP é uma plataforma de animação rápida e rica em recursos. A velocidade é uma ferramenta leve para melhorar drasticamente o desempenho de animação UI e o fluxo de trabalho.
- GSAP exige uma taxa de licenciamento para vários tipos de empresas. Velocity é gratuito e open source por meio da licença MIT ultrapermissiva.
- Em termos de performance, GSAP e Velocity são indistinguíveis em projetos do mundo real.
Minha recomendação é usar GSAP quando você necessitar de um controle preciso sobre over timing (por exemplo remapeamento, pausa/resumo/procurar), movimento (por exemplo, caminhos da Curva de Bezier), ou agrupamento/sequenciamento complexos. Essas características são fundamentais para o desenvolvimento do jogo e certos aplicativos de nicho, mas são menos comuns nas UI de web apps.
Velocity.js
Fazer referência ao rico conjunto de recursos do GSAP não quer dizer que o Velocity é fraca em recursos. Pelo contrário. Em apenas 7Kb quando compactados, Velocity não só reproduz todas as funcionalidades do $.animate() do jQuery, mas também empacota em animação colorida, transforms, loops, easings, animação classe e rolagem.
Em suma, Velocity é o melhor do que jQuery, jQuery UI e as transições CSS combinadas.
Além disso, do ponto de vista de conveniência, Velocity usa internamente o método do jQuery $.queue(), e assim interage perfeitamente com as funções $.animate(), $.fade() e $.delay do jQuery. E, uma vez que a sintaxe do Velocity é idêntica à do $.animate(), nenhum código da sua página precisa mudar.
Vamos dar uma rápida olhada em Velocity.js. Em um nível básico, Velocity funciona de forma idêntica ao $.animate():
$element .delay(1000) /* Use Velocity to animate the element's top property over a duration of 2000ms. */ .velocity({ top: "50%" }, 2000) /* Use a standard jQuery method to fade the element out once Velocity is done animating top. */ .fadeOut(1000);
Em seu nível mais avançado, podem ser criados cenários de rolagem complexos com animações 3D – com apenas duas linhas simples de código:
$element /* Scroll the browser to the top of this element over a duration of 1000ms. */ .velocity("scroll", 1000) /* Then rotate the element around its Y axis by 360 degrees. */ .velocity({ rotateY: "360deg" }, 1000);
Conclusão
O objetivo do Velocity é permanecer líder no desempenho de animação do DOM. Dê uma olhada em VelocityJS.org para saber mais sobre o último.
Antes de terminarmos, lembre-se de que uma interface performática vai além de escolher a biblioteca de animação certa. O resto da sua página também deve ser otimizado. Saiba mais a partir destas palestras fantásticas do Google: