
Ao desenvolver sites que vão além da estrutura de uma inicialização condicional, mais cedo ou mais tarde surgem perguntas sobre o desempenho das animações. Eles são especialmente importantes em sites de design, como os que se enquadram nos catálogos Awwwards, FWA, CSS Design Awards, etc. Nesse caso, geralmente a tarefa de criar animações e subsequente otimização, se necessário, recai sobre os ombros de desenvolvedores pouco experientes que nem sabem por onde começar. Geralmente, tudo isso se traduz em locais inibitórios que não podem ser usados e na subsequente atitude negativa em relação a toda a classe de tais projetos. Neste artigo, tentaremos descobrir onde está o limite do desempenho aceitável da animação, quais gargalos são comuns e onde procurar nas ferramentas do desenvolvedor em primeiro lugar.
Uma pequena observação: como este artigo se destina mais a desenvolvedores iniciantes e seu objetivo é mostrar abordagens gerais para otimizar animações, muitas coisas serão dadas de forma simplificada, não muito acadêmica.
Como o navegador exibe a página
Antes de tudo, é útil entender o que acontece quando o navegador nos exibe o estado atual da página. Existem quatro etapas principais:
- Cálculo de estilo (o navegador analisa seletores CSS, determina quais estilos devem ser aplicados a quais)
- Criação de layout (o layout da página é realmente formado)
- Pintura (cria representações em pixel de elementos para renderização subsequente)
- Composição da camada (o navegador reúne tudo e é exibido na tela)
Além disso, o navegador sempre atua nessa sequência e vai até o fim. Quando a página é exibida inicialmente após carregá-la, todas as quatro etapas passam. No futuro, nossas ações poderão causar a execução de uma delas, mas ao mesmo tempo todas as ações subsequentes serão executadas. Mas não os anteriores.
Consideraremos mais os gargalos de cada uma dessas etapas e agora faremos uma pergunta boba à primeira vista, a partir da qual, em teoria, você precisa começar ...
Se diminui ou não, essa é a questão ...
Muitas vezes, você pode conhecer pessoas que não estão fazendo nada com um site claramente lento e dizer "mas a velocidade da minha página dá 100 pontos, está tudo bem". Ou vice-versa, em um site que funcione bem, as pessoas estão envolvidas em algum tipo de otimização há muito tempo, porque algum algoritmo funciona ineficientemente com base em algumas métricas misteriosas. Mas entre esses extremos deve estar o meio do senso comum, então onde está?

Para conheça zen para entender se você precisa otimizar suas animações, é necessário realizar um pensamento filosófico profundo:
Se você vir que o site está lento, significa que está lento. Se você não perceber que o site está diminuindo a velocidade, não está diminuindo.
Por alguma razão, muitas pessoas acham essa afirmação muito estúpida, mas é isso? Para o usuário final, o desempenho não é um tipo de métrica ou algoritmo ideal com justificativa matemática rigorosa. Para ele, o desempenho é uma das duas coisas: diminui a velocidade ou não diminui.
Como ele determina isso? O olho de uma pessoa que passa muito tempo atrás do monitor começa a reagir bruscamente a uma queda nos fps. Isso causa uma estranha sensação de desconforto. Consequentemente, nossa tarefa, como desenvolvedores, é impedir o subsidência. O usuário está acostumado a ver o navegador funcionando a 60fps? Bem, então estamos fazendo tudo para que tudo continue assim. Pegamos um laptop com ferro médio e olhamos. Vemos muito menos do que 60fps - otimizamos. Vemos cerca de 60 - não toque em nada. O usuário não perceberá a diferença de qualquer maneira, e dedicaremos muito tempo à otimização por causa das otimizações.
Não faça otimizações para otimizações.
16.5ms
Expressar-se em termos de fps não é conveniente, então vamos para milissegundos. Com uma divisão simples de 1000ms / 60fps, obtemos cerca de 16,5ms de tempo por quadro.
O que isso significa? Por 16,5ms, o navegador deve nos mostrar o estado atual da página com a animação, seguindo as etapas que vimos acima e, ao mesmo tempo, os recursos devem permanecer para o trabalho de outros scripts, comunicação com o servidor etc. Se mais tempo for gasto na exibição do estado atual da página, veremos através de nossos olhos o atraso. Se cerca de 16ms, não haverá subsidência, mas é provável que a carga de ferro seja muito alta, os refrigeradores vibrem e os telefones aqueçam. Portanto, precisamos garantir que a renderização de um quadro não chegue perto desse valor no tempo e, melhor ainda, não seja superior a 10ms, para que haja uma margem no desempenho. Não esqueça que os testes são sempre realizados no hardware intermediário - por exemplo, nos exemplos a seguir, capturas de tela serão feitas no Pentium Silver com gráficos integrados.
Teste no hardware que seus usuários têm mais probabilidade de ter. Se você possui um processador de ponta e um farm de mineração embaixo da mesa em seu local de trabalho, tudo funcionará bem para você, enquanto seus usuários com laptops econômicos podem ficar muito tristes.
Para não confiar apenas nos seus bons olhos e intuição, é útil dominar as ferramentas do desenvolvedor, pelo menos em um nível básico. Eles não apenas fornecem dados de desempenho precisos, mas também informam onde procurar o problema, se tudo não funcionar muito bem.
Ferramentas de desenvolvedor no Google Chrome
Muitos codificadores são freqüentemente atingidos por ferramentas de desenvolvedor no navegador, mais do que no console linux. Mas, de fato, não há nada a temer. Sim, existem muitos botões, mas eles são redundantes para resolver nossos problemas. Agora veremos onde vale a pena prestar atenção, antes de tudo, para entender o que fazer com a animação e se é necessário fazer alguma coisa.
Quando se trata de desempenho, passaremos a maior parte do tempo na guia desempenho e pressionaremos o mesmo botão.

O atalho Ctrl-E ou o botão redondo à esquerda inicia e para de gravar o que está acontecendo. Os resultados são exibidos aqui. O navegador escreve muitas coisas, mas é melhor vê-lo uma vez do que lê-lo muitas vezes, então vamos pegar algum tipo de animação e dar uma olhada. Que seja uma animação CSS simples para iniciantes. Se você o abrir em tela cheia, poderá ver que ele funciona com atolamentos perceptíveis:
Vamos gravar alguns segundos no modo de tela cheia e ver o que acontece lá:

O navegador registra tudo o que faz. Na parte superior da janela, vemos um gráfico de fps. Ele pode ser facilmente usado para detectar uma anomalia se, no processo de trabalhar com a página, ela começar a ficar muito lenta. Se você clicar no gráfico com o mouse e puxá-lo para o lado ou girar a roda, poderá selecionar esse intervalo de tempo e as informações detalhadas serão exibidas abaixo. Em nosso exemplo simples, não há anomalias, mas é claramente visto que tudo não funciona de maneira muito uniforme.
Preste atenção imediatamente à linha Quadros , que contém informações sobre o tempo gasto em cada quadro. Você pode notar que esse tempo constantemente salta e excede significativamente 16ms (abaixo, em exemplos práticos, melhoraremos levemente essa animação).
A seguir, vemos várias linhas nas quais a carga é exibida em cores diferentes - você pode ver quanto tempo o navegador passou em diferentes tipos de atividades. Nossa animação é uniforme e as mesmas operações são realizadas para cada quadro, indicado por roxo e verde. Se você mover o mouse sobre os blocos coloridos, ficará claro que estamos lidando com os itens mencionados no início - o estilo de recálculo e a atualização da árvore de camadas são roxos e as camadas de pintura e composta são verdes.
Considere outra animação. Desta vez com scripts - um simples gerador de ruído. Este é um exemplo bastante ilustrativo, embora não seja de interesse do ponto de vista do design:
Você pode perceber que foram adicionados blocos amarelos que exibem a execução de scripts. Se houver muitas chamadas de função, para cada chamada mais um bloco será adicionado - pelo seu tamanho, é fácil encontrar a função "mais pesada", com a qual, provavelmente, a otimização deve começar.

No exemplo, o tempo gasto em um quadro varia em torno de 80ms. Mas o que existe, mesmo a olho nu, você pode ver claramente como tudo falha. Observando a seção de resumo abaixo, vemos que os scripts levam mais tempo. Comparado a eles, renderização e pintura parecem erros que podem ser negligenciados. Nem sempre, é claro, acontece, mas com bastante frequência.
Se você clicar no bloco marcado como chamada de função , abaixo está um link para a função no código do script. Se você passar por isso, poderá ver que neste exemplo há um ciclo através de todos os pixels na tela. Seria mais lógico executar essa tarefa em shaders, pois o desempenho seria muitas vezes melhor. Mas veremos isso em exemplos práticos.
O que fazer se ...
Aprendemos quais etapas existem ao exibir o estado atual de uma página em um navegador e onde ver qual delas leva mais tempo. É hora de se familiarizar com os motivos mais comuns pelos quais essa ou aquela etapa começa a exigir muitos recursos e dar algumas dicas sobre o que fazer neste ou naquele caso.
Cálculo de estilo
Se você perceber que já nesta etapa os problemas começam - provavelmente o ponto nem está na animação, mas no fato de haver muitos elementos na página. Nos sites de design, isso é bastante raro, geralmente esse problema é um satélite de grandes tabelas com milhares de elementos, mas se você ainda se deparar com isso:
Reduza o número de elementos na página, simplifique o layout. Preste atenção especial à repetição de trechos de código com wrappers, é provável que eles possam ser removidos.
O segundo motivo associado ao primeiro são seletores de CSS complexos. Se em páginas pequenas for possível usar aninhamento profundo, hacks complicados com elementos vizinhos etc., em uma página muito grande, tudo isso pode levar a um desempenho ruim.
Simplifique os seletores de CSS, use o BEM.
Criação de layout
Este item já está mais perto de design e animações, coisas interessantes começam aqui. A primeira coisa que é importante entender é que todo o layout é formado. Se mudarmos algo, ele será formado novamente. Por esse motivo, mesmo pequenas alterações na página grande podem causar atrasos visíveis nesta etapa.
A principal regra que nos guia ao criar animações é não permitir a reestruturação do layout a qualquer custo. Portanto, geralmente não tentamos otimizá-lo (e não há oportunidades específicas), ou seja, tentamos evitá-lo.
Existem muitas propriedades que podem causar a reconstrução do layout. Você pode encontrar listas na Internet, por exemplo, csstriggers.com não é ruim. Mais frequentemente do que outros nas animações, você pode encontrar propriedades:
display position / top / left / right / bottom width / height padding / margin border font-size / font-weight / line-height ...
Você pode perceber que todas essas propriedades estão unidas por uma coisa - elas descrevem as características geométricas dos elementos - exibem parâmetros, tamanho e localização física. Então, em vez de memorizar todos eles, lembre-se do que eles se referem.
Não altere as propriedades geométricas dos elementos, é melhor usar a transformação e a opacidade.
Separadamente, é importante notar que a alteração do plano de fundo do elemento também nos retornará a esta etapa. Eles estão constantemente esquecendo disso, então destacamos em uma recomendação separada:
Não altere os elementos de segundo plano.
Em alguns navegadores ( Eu não vou cutucar um dedo no Firefox ), um atraso típico de animações CSS com transformações pode aparecer, especialmente se mais de uma animação for executada por unidade de tempo. Externamente, isso pode parecer não apenas uma pausa em seu trabalho, mas também como "avarias" da animação desde o início. Parece que o navegador está constantemente recalculando alguma coisa. Esse comportamento quase sempre é corrigido usando a propriedade backface-visible .
Se possível, adicione backface-visible: oculto aos elementos animados.
Além disso, a reconstrução do layout é causada por nossas chamadas para elementos de scripts. Além disso, isso não precisa ser uma alteração direta no CSS; também pode ser um apelo a algumas propriedades e métodos de elementos. Os mais comuns são:
offset*** client*** inner*** scroll***
Nas animações, você deve ter cuidado com elas, porque se começarmos a nos referir a essas propriedades e métodos para um grande número de elementos, sempre que isso causar uma reestruturação do layout.
Evite consultar as propriedades e métodos mencionados para elementos individuais em loops.
Pintura e composição de camadas
Vamos considerar esses dois passos juntos, como eles estão um pouco relacionados e, geralmente, se houver problemas com um, estarão com o outro. Ignorar essas etapas, evitá-las, não funcionará, portanto, estamos tentando otimizá-las de alguma forma.
O navegador não prepara a imagem de pixel da página, mas em partes - as camadas. Pode haver muitos. Cada camada existe como se por si só e não afeta o restante, o que cria a base para alguns hacks CSS. Mas falaremos sobre eles outra vez. Em seguida, a imagem final é coletada dessas camadas. No contexto das animações, é muito útil colocar elementos animados em uma camada separada para que suas alterações não afetem tudo ao seu redor. É desejável que o conteúdo dos elementos seja pequeno. Podemos fazer isso usando a propriedade will-change ou, como fizemos antes, transformar: translateZ (0) . A única coisa a lembrar é que você não pode aumentar o número de camadas indefinidamente. Em algum momento, isso será um truque e o desempenho oposto cairá. Portanto, haverá duas dicas:
Use will-change ou transform: translateZ (0) para mover os elementos animados para uma camada separada.
mas ao mesmo tempo
Não exagere neste negócio. Verifique nas ferramentas do desenvolvedor que não é pior.
Muitas vezes, problemas sérios são causados por filtros que, de alguma forma, transformam a imagem dos elementos. Pode ser simples filtros CSS com opções de desfoque ou confusas com SVG, mas o efeito será o mesmo - uma diminuição perceptível no desempenho.
Não use filtros complexos. Se você ainda precisar do efeito pretendido, considere implementá-lo no WebGL.
Como essas dicas funcionam?
Eles funcionam, mas você não precisa esperar um milagre deles. Na rede, os novatos às vezes dizem: "Adicionei uma mudança de vontade, mas nada mudou". Normalmente, isso significa que o principal problema estava em outro lugar, e essa técnica proporcionou um aumento tão pequeno na produtividade que passou despercebida. É por isso que é importante usar as ferramentas do desenvolvedor para entender claramente exatamente onde está o gargalo e não gastar tempo e esforço tentando otimizar o que está funcionando bem.
De tudo isso, podemos concluir que não há muitas maneiras de influenciar a renderização de uma página, e o efeito delas nem sempre será significativo. Esses truques não são balas de prata, são necessários para polir a animação. Se olharmos para sites com desempenho realmente ruim, perceberemos que, na grande maioria dos casos, nossos próprios scripts são os culpados, e não problemas misteriosos com a análise de CSS em algum lugar nas entranhas do navegador.
Scripts ...
Você sabe onde os problemas com animações inibitórias crescem com mais frequência (de acordo com minhas observações)? Aqui a partir desta abordagem de desenvolvimento:

Parece bobagem, mas é. Constantemente existem soluções, obviamente copiadas de algum lugar completamente sem entender o que estava acontecendo. Até acontece que você pode remover metade do código e tudo continuará funcionando. Geralmente, o código nas respostas para SO ou Toaster não se destina à sua produção. Isso deveria ser óbvio. Ele mostra a idéia, responde à pergunta, mas não é a melhor opção final para sua tarefa específica.
Se você já estiver copiando, pelo menos procure no código ações desnecessárias.
RequestAnimationFrame
Eles costumam falar sobre esse método e recomendam usá-lo em vez de setTimeout / setInterval nas animações. Isso faz sentido, pois esses métodos têm a propriedade de estar fora de sincronia com os quadros que o navegador redesenha, resultando em pequenos atrasos. Mas existem dois pontos.
Primeiramente, se mais de um elemento for animado na página e chamarmos requestAnimationFrame muitas vezes, isso levará a um subsidência acentuado de fps. Em teoria, isso não deveria acontecer, mas, na prática, tudo acontece exatamente assim. Você pode se familiarizar com os testes aqui .
Combine todos os retornos de chamada de animação em um requestAnimationFrame.
O segundo ponto provavelmente está relacionado à situação em que já temos animação pesada, talvez com o uso de tela, da qual não podemos nos livrar ou não temos tempo para refazer, e acontece o seguinte: digamos que a animação deve ser concluída em N segundos e já usamos requestAnimationFrame . Mas são necessários muitos recursos para calcular o estado atual e vemos esta imagem: a animação funciona sem problemas, lindamente, mas em 2N ou 3N segundos. Como resultado, tudo é percebido muuuitoooooooo. Para corrigir esse comportamento, você pode contrariar todas as recomendações, usando setInterval / setTimeout e vincular os estados dos elementos animados ao tempo físico, e não aos quadros abstratos. Como resultado, obtemos uma diminuição formal em fps, mas com o efeito psicológico dos ganhos de produtividade.
No caso de animações extremamente lentas, pode fazer sentido recusar requestAnimationFrame em favor de setInterval / setTimeout.
Telas e Shaders
Uma parte significativa das animações em sites não padrão está relacionada à tela. Isso é compreensível, CSS é uma coisa limitada, mas aqui podemos realizar as fantasias de qualquer designer. Mas você precisa ter em mente que a tela 2D comum está longe de ser a tecnologia mais produtiva. Se você começar a desenhar vários elementos ou trabalhar com pixels diretamente, encontrará rapidamente o fato de que o fps está afundando ou, de repente, a pintura e a composição da camada começam a demorar indecentemente muito tempo. Este problema pode ser visto claramente no exemplo:
Vejamos o que o navegador faz (o mais recente Google Chrome no Linux):

Observe quanto a etapa de composição da camada foi expandida. Parece um pouco ilógico, porque existe apenas um elemento, o que pode ser montado lá por tanto tempo? Mas, ao usar a tela 2D, esse comportamento não é incomum, e algo a ver com isso é muito problemático. Essa é uma das razões pelas quais geralmente tendemos a usar o WebGL; não existem perguntas desse tipo.
Se houver uma escolha entre a tela 2D e o WebGL, escolha a segunda. Isso dará um bônus de desempenho inicial nas mesmas tarefas.
O que geralmente está associado ao WebGL? Com shaders. E depurar shaders é uma dor de cabeça para quem trabalha com eles. E as ferramentas de desenvolvedor aqui são praticamente impotentes. Geralmente, se houver muitos cálculos nos sombreadores, veremos no resumo abaixo que a maior parte do tempo é "simples", que na verdade é a execução de nossos sombreadores, independentemente do navegador, e não podemos obter detalhes úteis.
Existem recomendações diferentes sobre quais funções preferir sobre os shaders, porque elas são supostamente melhor otimizadas. Ou que as operações de bloqueio devem ser evitadas. Isso tudo é verdade, mas, de acordo com minhas observações, na maioria dos casos, os shaders que tornam o site lento demais são simplesmente shaders muito grandes. Se você escreveu 100 linhas GLSL em um só lugar, é quase garantido que funcione mal. E se também existem diferentes construções aninhadas, loops, tudo - a gravação se foi. É difícil fazer recomendações aqui, a menos que:
Se você percebeu durante o trabalho que tudo é mais complicado do que parecia inicialmente, e que haverá muito código e isso diminuirá a velocidade - é melhor discutir isso com o designer e o cliente o mais rápido possível e pensar no que pode ser alterado.
Muitas vezes, você pode concluir que um vídeo pré-preparado funcionará muito melhor do que tentar render algum tipo de coisa confusa em tempo real. Lembre-se disso. Sim, todo mundo quer se mostrar, quer se exibir "mas eu posso fazer assim", mas não se esqueça dos usuários finais.
Em conexão com esse pensamento, recordo a “doença” à qual a antiga Olimpíada é especialmente suscetível. Por alguma razão, ele se manifesta fortemente ao trabalhar com telas. Por esse motivo, você sempre deve copiar cuidadosamente o código dessas pessoas. Eles tentam usar os algoritmos matemáticos “corretos”, fórmulas físicas complexas, para calcular todos os movimentos dos elementos com grande precisão, mesmo onde são completamente inúteis. Isso leva a um aumento da carga no processador e ao fato de que, por nossos 10ms condicionais, não há tempo para contar nada. Na prática, muitas vezes você pode conviver com fórmulas aproximadas e conhecimento escolar de física. Não é necessário complicar as coisas, criamos sites, não software para mísseis balísticos.
Use algoritmos simples.
Há outro truque chamado RayMarching . Algumas pessoas consideram a criação de efeitos diferentes um desafio, um aquecimento para a mente e, às vezes, os resultados são impressionantes. Por exemplo, um mundo subaquático inteiro é gerado aqui (inseri um vídeo, porque, a partir de cálculos disso em tempo real, o telefone / laptop pode se desligar):
O shader em si pode ser encontrado aqui .
Na prática, tudo isso requer recursos incríveis para funcionar. No modo de tela cheia, temos 400-800ms por quadro (em geral, neste exemplo, pode ir até 1500ms):

Portanto, se você se pegou pensando em fazer algo assim em um site de combate, dê um teclado na cabeça, beba chá e pense em opções alternativas para implementar efeitos.
Não use RayMarching, esta é uma maneira de diminuir o desempenho.
Exemplo prático
Geralmente, não há exemplos suficientes em artigos sobre produtividade, mas pode ser difícil dizer uma palavra. Então considere um casal. Lembra do primeiro exemplo de túnel giratório CSS? O navegador fez várias coisas:

Queremos acelerar um pouco as coisas. Por onde começar? Vemos blocos roxos, o que significa que o navegador está constantemente reconstruindo o layout. Não há scripts lá, mas existem animações CSS nas quais algo muda. Vejamos o código deles:
@keyframes rotate { from { transform: rotate(0); } to { transform: rotate(360deg); } } @keyframes move-block { from { transform: translateX(0); background: @color1; } to { transform: translateX(-@block-size * 6); background: @color2; } }
As transformações não nos assustam, mas vemos uma mudança no fundo dos elementos. Lembramos que isso pode causar uma reestruturação do layout e pensamos o que pode ser feito nessa situação ...
A alteração do plano de fundo precisa ser removida a todo custo; portanto, com base na idéia geral de animação, decidimos que você pode colocar um gradiente radial no topo, o que criará quase o mesmo efeito de volume. Alguém dirá que os gradientes têm um efeito ruim no desempenho, mas não vamos mudar isso. Que seja melhor uma vez que afeta mal do que teremos uma montanha inteira de elementos que afetam constantemente. O resultado é:
Vamos ver o que o navegador faz:

Uau ... Em vez de várias ações, vemos chamadas raras para a GPU e nada mais, enquanto a animação em si começou a funcionar visivelmente mais suave.
Outro exemplo
Lembre-se da aparência do navegador no gerador de ruído:

O problema está definitivamente nos scripts. Pode-se ver que o bloco "render" é o maior. Esta é a nossa principal função para renderizar a imagem. Vamos olhar para ela:
function render() { let imageData = CTX.createImageData(CTX.canvas.width, CTX.canvas.height); for (let i = 0; i < imageData.data.length; i += 4) { const color = getRandom(); imageData.data[i] = color; imageData.data[i + 1] = color; imageData.data[i + 2] = color; imageData.data[i + 3] = 255; } CTX.putImageData(imageData, 0, 0); requestAnimationFrame(render); }
Definitivamente, há trabalhos em andamento com pixels individuais. Isso não é muito saudável. Dissemos que, se possível, é melhor usar não o 2d-canvas, mas o WebGL, e essa tarefa só quer ser paralelizada usando um shader. Vamos fazer isso:
Qual será o resultado? Veja você mesmo:

O tempo para um quadro diminuiu para quase 16ms. Claro que isso não é ideal, mas ainda melhor que 80ms. Em animações complexas e bonitas, esse ganho de desempenho pode ser muito perceptível. Aproveito a oportunidade para recomendar que os iniciantes se familiarizem com a introdução de shaders na programação e com a continuação de exemplos .
Conclusão
Neste artigo, descobrimos quando se trata de otimizar o desempenho das animações, como usar as ferramentas de desenvolvedor no Chrome nesse contexto e o que procurar primeiro. Espero que essas informações sejam úteis para desenvolvedores que enfrentam essas tarefas pela primeira vez e não sabem por onde começar.