Saiba como medir animações, pensar em frames de animação e melhorar a fluidez geral da página.
Você provavelmente já viu 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ções, além de fazer melhorias constantes nos diagnósticos do pipeline de renderização no Chromium.
Queremos compartilhar alguns avanços recentes, oferecer orientações concretas sobre ferramentas e discutir ideias para futuras métricas de suavidade de animação. Como sempre, adoraríamos receber seu feedback.
Esta postagem vai abordar três temas principais:
- Uma visão rápida das animações e dos frames de animação.
- Nossas ideias atuais sobre como medir a fluidez geral da animação.
- Algumas sugestões práticas para você aproveitar as ferramentas de laboratório hoje.
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 uma experiência mais natural, compreensível e divertida.
No entanto, animações mal implementadas ou em excesso 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 na verdade prejudicam a experiência quando têm um desempenho ruim. Portanto, alguns usuários podem preferir movimento reduzido, uma preferência do usuário que você precisa respeitar.
Como as animações funcionam?
Como um breve resumo, o pipeline de renderização consiste em alguns estágios sequenciais:
- Estilo:calcule os estilos que se aplicam aos elementos.
- Layout:gere a geometria e a posição de cada elemento.
- Pintar:preencha os pixels de cada elemento em camadas.
- Composição:desenhe as camadas na tela.
Embora haja muitas maneiras de definir animações, todas funcionam fundamentalmente por uma das seguintes opções:
- Ajustar propriedades de layout.
- Ajustar propriedades de pintura.
- Ajustando propriedades compostas.
Como essas etapas 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 Performance de renderização para mais detalhes.
Embora seja conveniente animar propriedades de layout, isso tem custos, mesmo que eles 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 Web Animations e garantir que você anime propriedades compostas é uma ótima primeira etapa para garantir animações suaves e eficientes. Mas isso não garante a fluidez, 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 levam tempo para aparecer. Uma mudança visual leva a um novo frame de animação, que é renderizado na tela do usuário.
Mostra a atualização em algum intervalo, para que as atualizações visuais sejam agrupadas. Muitas telas são atualizadas em um intervalo de tempo fixo, como 60 vezes por segundo (ou seja, 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 taxas de atualização conforme necessário ou até mesmo oferecer taxas de frames 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 JavaScript com eficiência.
Em algum momento, pode ficar muito difícil concluir todas as atualizações visuais dentro do prazo alocado atribuído pela tela. Quando isso acontece, o navegador descarta um frame. A tela não fica preta, apenas se repete. Você vê a mesma atualização visual por um pouco mais de tempo, o mesmo frame de animação que foi apresentado na oportunidade de frame anterior.
Isso acontece com frequência. Ele não é necessariamente perceptível, especialmente para conteúdo estático ou semelhante a documentos, que é comum na plataforma da Web. Os frames descartados só ficam 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 um movimento suave.
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 exige muitos recursos para ser decodificado rapidamente no dispositivo de destino.
- Usar muitas camadas exige muita memória da GPU.
- Definir estilos CSS ou animações da Web 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, resultando em 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. requestAnimationFrame()
, ou "rAF", informa ao navegador que você quer realizar uma animação e pede uma oportunidade para fazer isso antes da próxima etapa de renderização do pipeline. Se a função de callback não for chamada no momento esperado, isso significa que uma renderização não foi executada e um ou mais frames foram ignorados. Ao fazer pesquisas e contar com que frequência o rAF é chamado, você pode calcular uma espécie de métrica de "quadros 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);
Não é uma boa ideia usar a pesquisa requestAnimationFrame()
por vários motivos:
- Cada script precisa configurar o próprio loop de pesquisa.
- Ele pode bloquear o caminho crítico.
- Mesmo que a sondagem rAF seja rápida, ela pode impedir que o
requestIdleCallback()
agende blocos ociosos longos quando usado continuamente (blocos que excedem um único frame). - Da mesma forma, a falta de blocos longos de inatividade impede que o navegador agende outras tarefas de longa duração (como 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 gerar 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 Jank Sample para saber como uma animação orientada por rAF, quando há muito trabalho na linha de execução principal (como layout), vai resultar em frames descartados, menos callbacks de rAF e FPS mais baixo.
Quando a linha de execução principal fica sobrecarregada, as atualizações visuais começam a falhar. Isso é jank!
Muitas ferramentas de medição se concentraram na capacidade da thread principal de gerar de maneira oportuna e na 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
thread principal. Essas tarefas longas prejudicam completamente a capacidade da página de fornecer
determinados tipos de atualizações visuais. No canto superior esquerdo, você pode ver uma
queda correspondente de requestAnimationFrame()
QPS informados para 0.
Mesmo com essas tarefas longas, a página continua rolando sem problemas. Isso acontece porque, em navegadores modernos, a rolagem geralmente é encadeada, sendo totalmente controlada 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. Quando 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 sondagem rAF sugeriu uma queda de frame para 0, mas visualmente, um usuário não conseguiria notar uma 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()
.
Então, quando as atualizações e os frames de animação são importantes? Confira alguns critérios que estamos considerando e gostaríamos de receber feedback sobre eles:
- Atualizações das linhas de execução principal e do compositor
- Atualizações de pintura ausentes
- Detectar animações
- Qualidade x quantidade
Atualizações das linhas de execução principal e do compositor
As atualizações de frames de animação não são booleanas. Não é o caso de que os frames só podem ser totalmente descartados ou totalmente apresentados. Há muitos motivos para um frame de animação ser parcialmente apresentado. Em outras palavras, ele pode ter simultaneamente algum conteúdo desatualizado e algumas novas atualizações visuais que são apresentadas.
O exemplo mais comum disso é quando o navegador não consegue produzir uma nova atualização da thread principal dentro do prazo do frame, mas tem uma nova atualização da thread do compositor (como o exemplo de rolagem com linhas de execução de antes).
Um motivo importante para usar animações declarativas em 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 produzindo atualizações visuais de maneira eficiente e paralela.
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 depois de perder vários prazos de frames. Nesse caso, o navegador terá alguma atualização, mas talvez não seja a mais recente.
De modo geral, consideramos como frame parcial aqueles que contêm algumas atualizações visuais novas, mas não todas. 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ó acontece se elas forem controladas pela linha de execução do compositor.
Atualizações de pintura ausentes
Outro tipo de atualização parcial ocorre quando mídias como imagens não terminaram de decodificar e rasterizar 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 acontece porque as renderizações de pixel de conteúdo além da janela de visualização visível podem ser descartadas para economizar memória da GPU. Leva tempo para renderizar pixels, e pode levar mais tempo do que um único frame para renderizar tudo após uma rolagem longa, como um movimento rápido do dedo. Isso é conhecido como checkerboarding.
Com cada oportunidade de renderização de frame, é possível rastrear quantos dos updates visuais mais recentes realmente chegaram à tela. Medir a capacidade de fazer isso em muitos frames (ou tempo) é conhecido como taxa de transferência de frames.
Se a GPU estiver muito sobrecarregada, o navegador (ou plataforma) poderá até mesmo começar a limitar a taxa em que tenta fazer atualizações visuais e, assim, diminuir as taxas de frames efetivas. Embora isso possa reduzir tecnicamente o número de atualizações de frames descartadas, visualmente ainda vai parecer uma taxa de transferência de frames menor.
No entanto, nem todos os tipos de baixa taxa de transferência de frames são ruins. Se a página estiver principalmente inativa e não houver animações ativas, um frame rate baixo será tão atraente visualmente quanto um frame rate alto (e pode economizar bateria!).
Então, quando a capacidade de frames é importante?
Detectar animações
Uma alta capacidade de processamento de frames é importante, principalmente durante períodos com animações importantes. Diferentes tipos de animação dependem de atualizações visuais de uma thread específica (principal, compositor ou worker). Portanto, a atualização visual depende de essa thread fornecer a atualização dentro do prazo. Dizemos que uma determinada linha de execução afeta a suavidade sempre que há uma animação ativa que depende da atualização dessa 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 animações controladas pela entrada do usuário, são mais claras de definir do que as animações controladas por JavaScript implementadas como atualizações periódicas de propriedades de estilo animáveis.
Mesmo com requestAnimationFrame()
, não é possível presumir que toda chamada rAF necessariamente produz 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 deve afetar as medições de suavidade, já que não há 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 ver uma taxa de frames constante de 60 fps ao assistir um vídeo. Tecnicamente, isso é perfeitamente suave, mas o vídeo em si pode ter uma taxa de bits baixa ou problemas com o armazenamento em buffer da rede. Isso não é capturado diretamente pelas métricas de suavidade da animação, mas ainda pode ser desagradável para o usuário.
Ou um jogo que usa <canvas>
(talvez até usando técnicas como
canvas
fora da tela para
garantir uma taxa de frames constante) pode tecnicamente ser perfeitamente suave em termos de
frames de animação, sem carregar recursos de alta qualidade na
cena ou exibir artefatos de renderização.
E, claro, um site pode ter animações muito ruins 🙂
Quero dizer, 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 podem ser descartados de maneiras que não afetam a fluidez, começamos a pensar em cada frame como tendo uma pontuação de integridade ou fluidez.
Confira o espectro de maneiras de interpretar o estado de um único frame de animação, ordenado do melhor ao pior caso:
Nenhuma atualização desejada | Tempo ocioso, repetição do frame anterior. |
Totalmente apresentado | A atualização da linha de execução principal foi confirmada dentro do prazo ou não era desejada. |
Parcialmente apresentado | Somente compositor. A atualização atrasada da linha de execução principal não teve mudança visual. |
Parcialmente apresentado | Somente compositor. A linha de execução principal teve uma atualização visual, mas ela não incluiu uma animação que afeta a suavidade. |
Parcialmente apresentado | 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. |
Parcialmente apresentado | Somente compositor; sem a atualização principal desejada, e a atualização do compositor tem uma animação que afeta a suavidade. |
Parcialmente apresentado | Somente compositor, mas a atualização não tem uma animação que afete a suavidade. |
Frame ignorado | Nenhuma atualização. Não houve a atualização do compositor desejada, e o principal foi atrasado. |
Frame ignorado | Uma atualização do compositor foi desejada, mas foi adiada. |
Frame desatualizado | Uma atualização foi desejada e produzida pelo renderizador, mas a GPU ainda não a apresentou antes do prazo de vsync. |
É possível transformar esses estados em uma espécie de pontuação. Uma maneira de interpretar essa pontuação é considerá-la uma probabilidade de ser observável pelo usuário. Um único frame descartado pode não ser muito perceptível, mas uma sequência de muitos frames descartados afetando a fluidez em seguida certamente é!
Juntando tudo: uma métrica de porcentagem de frames descartados
Embora às vezes seja necessário analisar o estado de cada frame de animação, também é útil atribuir uma pontuação rápida "de relance" a uma experiência.
Como os frames podem ser parcialmente apresentados e porque mesmo os frames totalmente ignorados podem não afetar a fluidez, queremos nos concentrar menos na contagem de frames e mais na extensão em que o navegador não consegue fornecer atualizações visualmente completas quando isso é importante.
O modelo mental deve mudar de:
- Quadros por segundo, para
- Detectar atualizações de animação ausentes e importantes para
- Porcentagem de desistências em um determinado período.
O que importa é a proporção de tempo esperando 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 desistência:para todos os frames de animação não inativos ao longo de toda a linha do tempo.
- Pior caso de porcentagem de frames descartados:medido em janelas deslizantes de um segundo.
- 95º percentil de porcentagem de frames descartados:medido em janelas deslizantes de 1 segundo.
Você pode encontrar essas pontuações em algumas ferramentas para desenvolvedores do Chrome hoje. Embora essas métricas se concentrem apenas na taxa de transferência geral de frames, também estamos avaliando outros fatores, como a latência de frames.
Teste por conta própria nas ferramentas para desenvolvedores.
HUD de performance
O Chromium tem um HUD de desempenho interessante oculto atrás de uma flag
(chrome://flags/#show-performance-metrics-hud
). Nele, você encontra pontuações
em tempo real para itens como Core Web Vitals e também algumas definições experimentais
para a suavidade da animação com base na porcentagem de frames descartados ao longo do tempo.
Estatísticas de renderização de frames
Ative "Estatísticas de renderização de frames" nas configurações de renderização do DevTools para conferir uma visualização em tempo real dos novos frames de animação, que são codificados por cores para diferenciar atualizações parciais de atualizações de frames totalmente descartados. A taxa de quadros por segundo informada é apenas para frames totalmente apresentados.
Visualizador de frames nas gravações de perfil de desempenho do DevTools
O painel "Performance" do DevTools tem um visualizador de frames há muito tempo. No entanto, ele ficou um pouco dessincronizado com o funcionamento do pipeline de renderização moderno. Houve muitas melhorias recentes, até mesmo no mais recente Chrome Canary, que acreditamos que vai facilitar muito a depuração de problemas de animação.
Agora, os frames no visualizador 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 de todas as nuances descritas acima, mas planejamos adicionar mais no futuro próximo.
Rastreamento do Chrome
Por fim, com o Chrome Tracing, a ferramenta ideal para analisar detalhes, você pode gravar um rastreamento de "Renderização de conteúdo da Web" usando a nova interface do Perfetto (ou about:tracing
) e analisar a fundo o pipeline de gráficos do Chrome. Pode ser uma tarefa difícil, mas há algumas coisas
adicionadas recentemente ao Chromium para facilitar. Confira uma visão geral do que está disponível no documento Ciclo de vida de um frame.
Com os eventos de rastreamento, é possível determinar:
- Quais animações estão em execução (usando eventos chamados
TrackerValidation
). - Receber a linha do tempo exata dos frames de animação (usando eventos chamados
PipelineReporter
). - Para atualizações de animação instáveis, descubra exatamente o que está impedindo que sua
animação seja executada mais rapidamente (usando os detalhamentos de eventos em
eventos
PipelineReporter
). - Para animações baseadas em entrada, veja quanto tempo leva para receber uma atualização visual
(usando eventos chamados
EventLatency
).
A seguir
A iniciativa Web Vitals (em inglês) 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 essenciais para detectar e diagnosticar possíveis problemas de interatividade. Estamos planejando criar uma métrica semelhante baseada em laboratório para a suavidade da animação.
Vamos manter você atualizado enquanto trabalhamos 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 suavidade da animação de maneira eficiente para usuários reais no campo e no laboratório. Fique de olho nas atualizações por lá também.
Feedback
Estamos animados 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 conte para nós o que você achou.
Envie seus comentários para o grupo do Google web-vitals-feedback com "[Métricas de suavidade]" na linha de assunto. Queremos saber sua opinião!