Em relação a uma métrica de suavidade da animação

Saiba como medir animações, pensar em frames de animação e melhorar a fluidez da página.

Behdad Bakhshinategh
Behdad Bakhshinategh
Jonathan Ross
Jonathan Ross
Michal Mocny
Michal Mocny

Você provavelmente já encontrou páginas que "travam" ou "congelam" durante a rolagem ou animações. Gostamos de dizer que essas experiências não são tranquilas. Para resolver esses tipos de problemas, a equipe do Chrome tem trabalhado para adicionar mais suporte às ferramentas de laboratório para detecção de animação, além de fazer melhorias constantes no diagnóstico do pipeline de renderização no Chromium.

Queremos compartilhar alguns progressos recentes, oferecer orientações concretas sobre ferramentas e discutir ideias para futuras métricas de fluidez de animação. Como sempre, adoraríamos saber sua opinião. Comente aqui.

Esta postagem vai abordar três tópicos principais:

  • Uma breve análise de animações e frames de animação.
  • Nossas ideias atuais sobre como medir a fluidez geral da animação.
  • Algumas sugestões práticas para você aproveitar hoje mesmo nas ferramentas de laboratório.

O que são animações?

As animações dão vida ao conteúdo. Ao fazer o conteúdo se mover, especialmente em resposta às interações do usuário, as animações podem tornar a experiência mais natural, compreensível e divertida.

No entanto, animações mal implementadas ou a adição de muitas delas podem degradar a experiência e torná-la nada divertida. Provavelmente, todos nós já interagimos com uma interface que adicionou muitos efeitos de transição "úteis", que se tornam hostis para a experiência quando têm um desempenho ruim. Alguns usuários podem preferir a redução de movimento, uma preferência do usuário que você precisa respeitar.

Como funcionam as animações?

Para recapitular rapidamente, o pipeline de renderização consiste em alguns estágios sequenciais:

  1. Estilo:calcule os estilos aplicados aos elementos.
  2. Layout:gere a geometria e a posição de cada elemento.
  3. Pintar:preencha os pixels de cada elemento em camadas.
  4. Composição:mostra as camadas na tela.

Embora haja muitas maneiras de definir animações, todas funcionam basicamente de uma das seguintes formas:

  • Ajustar as propriedades do layout.
  • Ajustar as propriedades paint.
  • Ajuste as propriedades compostas.

Como esses estágios são sequenciais, é importante definir animações em termos de propriedades que estão mais abaixo no pipeline. Quanto mais cedo a atualização acontecer no processo, maiores serão os custos e menor será a probabilidade de ela ser tranquila. Consulte Desempenho de renderização para mais detalhes.

Embora possa ser conveniente animar propriedades de layout, isso tem um custo, mesmo que esses custos não sejam imediatamente aparentes. As animações precisam ser definidas em termos de mudanças de propriedades compostas sempre que possível.

Definir animações CSS declarativas ou usar animações Web e garantir que você anime propriedades compostas é uma ótima primeira etapa para garantir animações suaves e eficientes. No entanto, isso por si só não garante avidez, porque até mesmo animações da Web eficientes têm limites de desempenho. Por isso, é sempre importante medir.

O que são frames de animação?

As atualizações na representação visual de uma página demoram para aparecer. Uma mudança visual leva a um novo frame de animação, que é renderizado na tela do usuário.

A atualização é mostrada em algum intervalo, para que as atualizações visuais sejam agrupadas. Muitas telas são atualizadas em um intervalo fixo de tempo, como 60 vezes por segundo (ou 60 Hz). Algumas telas mais modernas podem oferecer taxas de atualização mais altas (90 a 120 Hz estão se tornando comuns). Muitas vezes, essas telas podem se adaptar ativamente entre as taxas de atualização conforme necessário ou até mesmo oferecer taxas de quadros totalmente variáveis.

O objetivo de qualquer aplicativo, como um jogo ou um navegador, é processar todas essas atualizações visuais em lote e produzir um frame de animação visualmente completo dentro do prazo, sempre. Essa meta é totalmente diferente de outras tarefas importantes do navegador, como carregar conteúdo da rede rapidamente ou executar tarefas do JavaScript com eficiência.

Em algum momento, pode se tornar muito difícil concluir todas as atualizações visuais dentro do prazo estipulado pela tela. Quando isso acontece, o navegador descarta um frame. A tela não fica preta, apenas se repete. Você vai ver a mesma atualização visual por mais um tempo, o mesmo frame de animação que foi apresentado na oportunidade anterior.

Isso acontece com frequência. Ele nem sempre é perceptível, especialmente para conteúdo estático ou semelhante a documentos, o que é comum na plataforma da Web, em particular. Os frames perdidos só se tornam aparentes quando há atualizações visuais importantes, como animações, para as quais precisamos de um fluxo constante de atualizações de animação para mostrar movimentos suaves.

O que afeta os frames de animação?

Os desenvolvedores da Web podem afetar muito a capacidade de um navegador renderizar e apresentar atualizações visuais de forma rápida e eficiente.

Alguns exemplos:

  • Usar conteúdo muito grande ou que consome muitos recursos para decodificar rapidamente no dispositivo de destino.
  • Usar muitas camadas que exigem muita memória de GPU.
  • Definir animações ou estilos CSS muito complexos.
  • Usar antipadrões de design que desativam otimizações de renderização rápida.
  • Muito trabalho de JS na linha de execução principal, levando a tarefas longas que bloqueiam atualizações visuais.

Mas como saber quando um frame de animação perdeu o prazo e causou um frame descartado?

Um método possível é usar a pesquisa requestAnimationFrame(), mas ela tem várias desvantagens. O requestAnimationFrame(), ou "rAF", informa ao navegador que você quer executar uma animação e pede uma oportunidade para fazer isso antes do próximo estágio de pintura do pipeline de renderização. Se a função de callback não for chamada no momento esperado, significa que uma pintura não foi executada e um ou mais frames foram ignorados. Ao consultar e contar a frequência com que o rAF é chamado, você pode calcular uma espécie de métrica "frames por segundo" (FPS).

let frameTimes = [];
function pollFramesPerSecond(now) {
  frameTimes = [...frameTimes.filter(t => t > now - 1000), now];
  requestAnimationFrame(pollFramesPerSecond);
  console.log('Frames per second:', frameTimes.length);
}
requestAnimationFrame(pollFramesPerSecond);

O uso da pesquisa requestAnimationFrame() não é uma boa ideia por vários motivos:

  • Cada script precisa configurar o próprio loop de pesquisa.
  • Isso pode bloquear o caminho crítico.
  • Mesmo que a pesquisa rAF seja rápida, ela pode impedir que requestIdleCallback() programe blocos ociosos longos quando usada continuamente (blocos que ultrapassam um único frame).
  • Da mesma forma, a falta de blocos ociosos longos impede que o navegador programe outras tarefas de longa duração (como a coleta de lixo mais longa e outros trabalhos em segundo plano ou especulativos).
  • Se a pesquisa for ativada e desativada, você vai perder casos em que o orçamento de frames foi excedido.
  • A pesquisa vai informar falsos positivos nos casos em que o navegador estiver usando frequência de atualização variável (por exemplo, devido ao status de energia ou visibilidade).
  • E, mais importante, ele não captura todos os tipos de atualizações de animação.

Muito trabalho na linha de execução principal pode afetar a capacidade de ver frames de animação. Confira o Exemplo de intermitência para saber como uma animação orientada por rAF, quando há muito trabalho na linha de execução principal (como layout), leva a frames descartados e a menos callbacks de rAF, além de FPS mais baixos.

Quando a linha de execução principal fica congestionada, as atualizações visuais começam a falhar. Isso é instável.

Muitas ferramentas de medição se concentraram na capacidade da linha de execução principal renderizar em tempo hábil e na capacidade de execução suave dos frames de animação. Mas essa não é a história toda. Veja o exemplo a seguir:

O vídeo acima mostra uma página que injeta periodicamente tarefas longas na linha de execução principal. Essas tarefas longas estragam completamente a capacidade da página de fornecer certos tipos de atualizações visuais. No canto superior esquerdo, é possível ver uma queda correspondente de QPS relatados de requestAnimationFrame() para 0.

No entanto, apesar dessas tarefas longas, a página continua rolando sem problemas. Isso acontece porque, em navegadores modernos, a rolagem geralmente é encadeada, gerada inteiramente pelo compositor.

Este é um exemplo que contém simultaneamente muitos frames descartados na linha de execução principal, mas ainda tem muitos frames de rolagem entregues com sucesso na linha de execução do compositor. Depois que a tarefa longa é concluída, a atualização de pintura da linha de execução principal não tem nenhuma mudança visual para oferecer. A pesquisa rAF sugeriu uma queda de frame para 0, mas visualmente, um usuário não conseguiria notar a diferença.

Para frames de animação, a história não é tão simples.

Frames de animação: atualizações importantes

O exemplo acima mostra que há mais na história do que apenas requestAnimationFrame().

Quando as atualizações e os frames de animação são importantes? Confira alguns critérios que estamos considerando e sobre os quais gostaríamos de receber feedback:

  • Atualizações da linha de execução principal e do compositor
  • Atualizações de pintura ausentes
  • Como detectar animações
  • Qualidade x quantidade

Atualizações da linha de execução principal e do compositor

As atualizações de frames de animação não são booleanas. Os frames não podem ser totalmente descartados ou apresentados. Há muitos motivos para um frame de animação ser parcialmente apresentado. Em outras palavras, ele pode ter algum conteúdo desatualizado e algumas novas atualizações visuais ao mesmo tempo.

O exemplo mais comum disso é quando o navegador não consegue produzir uma nova atualização da linha de execução principal dentro do prazo do frame, mas tem uma nova atualização da linha de execução do compositor (como o exemplo de rolagem em linha de execução anterior).

Uma razão importante para usar animações declarativas para animar propriedades compostas é que isso permite que uma animação seja totalmente controlada pela linha de execução do compositor, mesmo quando a linha de execução principal está ocupada. Esses tipos de animações podem continuar a produzir atualizações visuais de maneira eficiente e em paralelo.

Por outro lado, pode haver casos em que uma atualização da linha de execução principal finalmente fica disponível para apresentação, mas somente após o término de vários prazos de frame. Aqui, o navegador terá algumas atualizações novas, mas talvez não seja a mais recente.

De modo geral, consideramos que os frames que contêm algumas novas atualizações visuais, sem todas as novas atualizações visuais, são parciais. Frames parciais são bastante comuns. O ideal é que as atualizações parciais incluam pelo menos as atualizações visuais mais importantes, como animações, mas isso só pode acontecer se as animações forem controladas pela linha de execução do compositor.

Atualizações de pintura ausentes

Outro tipo de atualização parcial é quando mídias como imagens não terminaram a decodificação e rasterização a tempo para a apresentação do frame.

Ou, mesmo que uma página seja perfeitamente estática, os navegadores ainda podem ficar para trás na renderização de atualizações visuais durante a rolagem rápida. Isso ocorre porque as renderizações de pixel do conteúdo além da viewport visível podem ser descartadas para economizar memória da GPU. A renderização de pixels leva tempo, e pode levar mais tempo do que um único frame para renderizar tudo depois de uma rolagem grande, como um movimento rápido do dedo. Isso é conhecido como grade.

Com cada oportunidade de renderização de frame, é possível acompanhar a quantidade de atualizações visuais mais recentes que realmente chegaram à tela. A capacidade de fazer isso em muitos frames (ou tempo) é conhecida como throughput de frame.

Se a GPU estiver realmente sobrecarregada, o navegador (ou a plataforma) poderá até reduzir a taxa de tentativas de atualizações visuais e, assim, diminuir as taxas de frames efetivas. Embora tecnicamente isso possa reduzir o número de atualizações de frames descartados, visualmente ainda vai aparecer como uma menor taxa de frames.

No entanto, nem todos os tipos de baixa taxa de frames são ruins. Se a página estiver quase ociosa e não houver animações ativas, uma taxa de frames baixa será tão visualmente atrativa quanto uma taxa de frames alta (e pode economizar bateria).

Quando a taxa de frames importa?

Como detectar animações

A capacidade de processamento de frames alta é importante, principalmente durante períodos com animações importantes. Diferentes tipos de animação dependem de atualizações visuais de uma linha de execução específica (principal, compositor ou worker). Portanto, a atualização visual depende dessa linha de execução para fornecer a atualização dentro do prazo. Dizemos que uma determinada linha de execução afeta a fluidez sempre que há uma animação ativa que depende dessa atualização de linha de execução.

Alguns tipos de animações são mais fáceis de definir e detectar do que outros. As animações declarativas, ou baseadas na entrada do usuário, são mais fáceis de definir do que as animações baseadas em JavaScript implementadas como atualizações periódicas para propriedades de estilo animáveis.

Mesmo com requestAnimationFrame(), não é possível presumir que todas as chamadas de rAF produzem necessariamente uma atualização visual ou animação. Por exemplo, usar a pesquisa rAF apenas para rastrear a taxa de frames (como mostrado acima) não afeta as medições de suavidade, já que não há uma atualização visual.

Qualidade x quantidade

Por fim, a detecção de animações e atualizações de frames de animação ainda é apenas parte da história, porque captura apenas a quantidade de atualizações de animação, não a qualidade.

Por exemplo, você pode notar uma taxa de atualização estável de 60 qps ao assistir um vídeo. Tecnicamente, isso é perfeitamente suave, mas o vídeo pode ter uma taxa de bits baixa ou problemas com o armazenamento em buffer de rede. Isso não é capturado diretamente pelas métricas de fluidez da animação, mas ainda pode ser perturbador para o usuário.

Ou um jogo que usa <canvas> (talvez até usando técnicas como offscreen canvas para garantir uma taxa de frames estável) pode ser tecnicamente perfeito em termos de frames de animação, mas falhar ao carregar recursos de alta qualidade do jogo na cena ou exibir artefatos de renderização.

E, claro, um site pode ter animações muito ruins 🙂

GIF de construção de escola antiga

Acho que eles eram bem legais para a época.

Estados de um único frame de animação

Como os frames podem ser apresentados parcialmente ou descartados de maneiras que não afetam a fluidez, começamos a pensar em cada frame como tendo uma pontuação de completude ou fluidez.

Confira o espectro de maneiras de interpretar o estado de um único frame de animação, ordenado do melhor para o pior caso:

Não há atualização desejada Tempo ocioso, repetição do frame anterior.
Apresentação completa A atualização da linha de execução principal foi confirmada dentro do prazo ou nenhuma atualização da linha de execução principal foi desejada.
Apresentado parcialmente Apenas compositor. A atualização atrasada da linha de execução principal não teve mudança visual.
Apresentado parcialmente Somente compositor: a linha de execução principal teve uma atualização visual, mas essa atualização não incluiu uma animação que afeta a fluidez.
Apresentado parcialmente Somente compositor: a linha de execução principal teve uma atualização visual que afeta a fluidez, mas um frame desatualizado chegou e foi usado.
Apresentado parcialmente Somente compositor, sem a atualização principal desejada, e a atualização do compositor tem uma animação que afeta a fluidez.
Apresentado parcialmente Apenas o compositor, mas a atualização do compositor não tem uma animação que afete a fluidez.
Frame ignorado Nenhuma atualização. Não houve nenhuma atualização de compositor desejada, e a main foi atrasada.
Frame ignorado Uma atualização do compositor foi desejada, mas foi adiada.
Frame desatualizado Uma atualização foi desejada, ela foi produzida pelo renderizador, mas a GPU ainda não a apresentou antes do prazo do VSync.

É possível transformar esses estados em uma espécie de pontuação. Talvez uma maneira de interpretar essa pontuação seja considerá-la uma probabilidade de ser observada pelo usuário. Um único frame perdido pode não ser muito perceptível, mas uma sequência de muitos frames perdidos que afetam a fluidez em uma linha com certeza é.

Como tudo funciona em conjunto: uma métrica de porcentagem de frames perdidos

Embora às vezes seja necessário se aprofundar no estado de cada frame de animação, também é útil atribuir uma pontuação rápida "de relance" para uma experiência.

Como os frames podem ser parcialmente apresentados e porque mesmo as atualizações de frames totalmente ignoradas podem não afetar a fluidez, queremos nos concentrar menos em contar frames e mais na extensão em que o navegador não consegue fornecer atualizações completas visualmente quando necessário.

O modelo mental precisa passar de:

  1. Quadros por segundo, para
  2. Detectar atualizações de animação importantes e ausentes para
  3. Porcentagem de desistências em um determinado período.

O que importa é a proporção de tempo de espera por atualizações importantes. Acreditamos que isso corresponde à maneira natural como os usuários experimentam a fluidez do conteúdo da Web na prática. Até agora, usamos o seguinte como um conjunto inicial de métricas:

  • Porcentagem média de quadros perdidos:para todos os quadros de animação que não estão inativos em toda a linha do tempo.
  • Pior caso de frames descartados em porcentagem:medido em janelas de tempo deslizantes de 1 segundo.
  • 95º percentil de frames descartados em porcentagem:medido em janelas deslizantes de 1 segundo.

Já é possível encontrar essas pontuações em algumas ferramentas para desenvolvedores do Chrome. Embora essas métricas se concentrem apenas na taxa de frames geral, também avaliamos outros fatores, como a latência de frames.

Teste nas ferramentas para desenvolvedores.

HUD de desempenho

O Chromium tem um HUD de desempenho oculto por uma flag (chrome://flags/#show-performance-metrics-hud). Nele, você encontra pontuações ao vivo para itens como as Core Web Vitals e também algumas definições experimentais para aviliação de animação com base na porcentagem de frames perdidos ao longo do tempo.

HUD de desempenho

Estatísticas de renderização de frames

Ative "Frame Rendering Stats" nas Ferramentas do desenvolvedor nas configurações de renderização para ver uma visualização em tempo real de novos frames de animação, que são codificados por cores para diferenciar atualizações parciais de atualizações de frame totalmente descartadas. O fps informado é apenas para frames totalmente apresentados.

Estatísticas de renderização de frames

Visualizador de frames em gravações de perfil de desempenho do DevTools

O painel de desempenho do DevTools já tem um visualizador de frames. No entanto, ele ficou um pouco fora de sincronia com o funcionamento do pipeline de renderização moderno. Houve muitas melhorias recentes, até mesmo na versão mais recente do Chrome Canary, que acreditamos que vai facilitar muito a depuração de problemas de animação.

Hoje, os frames no visualizador de frames estão mais alinhados com os limites de vsync e codificados por cores com base no status. Ainda não há uma visualização completa para todas as nuances descritas acima, mas planejamos adicionar mais em breve.

Visualizador de frames no Chrome DevTools

Rastreamento do Chrome

Por fim, com o Chrome Tracing, a ferramenta de escolha para se aprofundar nos detalhes, é possível gravar um rastro de "renderização de conteúdo da Web" usando a nova interface do Perfetto (ou about:tracing) e se aprofundar no pipeline gráfico do Chrome. Essa pode ser uma tarefa assustadora, mas algumas coisas foram adicionadas recentemente ao Chromium para facilitar. Confira uma visão geral do que está disponível no documento Life of a Frame (link em inglês).

Com os eventos de rastreamento, você pode determinar definitivamente:

  • Quais animações estão em execução (usando eventos com o nome TrackerValidation).
  • Receber a linha do tempo exata dos frames de animação (usando eventos PipelineReporter).
  • Para atualizações de animação instáveis, descubra exatamente o que está impedindo a animação de ser executada mais rapidamente (usando as descrições de eventos dentro dos eventos PipelineReporter).
  • Para animações com base na entrada, confira quanto tempo leva para receber uma atualização visual (usando eventos com o nome EventLatency).

Relatório do pipeline do Chrome Tracing

A seguir

A iniciativa Web Vitals tem como objetivo fornecer métricas e orientações para criar ótimas experiências do usuário na Web. Métricas baseadas em laboratório, como o Tempo total de bloqueio (TBT), são vitais para detectar e diagnosticar possíveis problemas de interatividade. Planejamos criar uma métrica semelhante baseada em laboratório para avidez da animação.

Vamos continuar trabalhando em ideias para criar uma métrica abrangente com base em dados de frames de animação individuais.

No futuro, também queremos criar APIs que permitam medir a fluidez da animação com eficiência para usuários reais no campo e no laboratório. Fique de olho nas atualizações.

Feedback

Estamos felizes com todas as melhorias recentes e ferramentas para desenvolvedores que foram lançadas no Chrome para medir a fluidez da animação. Teste essas ferramentas, compare suas animações e nos diga o que achou.

Você pode enviar seus comentários para o grupo web-vitals-feedback do Google com "[Smoothness Metrics]" na linha de assunto. Queremos saber sua opinião.