Otimizar a interação com a próxima exibição

Saiba como otimizar a interação do seu site com a Next Paint.

Interação com a próxima pintura (INP, na sigla em inglês) é uma métrica estável da Core Web Vitals que avalia a capacidade de resposta geral de uma página em relação às interações do usuário observando a latência de todas as interações qualificadas que ocorrem durante a vida útil de uma visita do usuário a uma página. O valor final de INP é a interação mais longa observada (às vezes ignorando outliers).

Para oferecer uma boa experiência ao usuário, os sites precisam ter uma interação com a próxima pintura de 200 milissegundos ou menos. Para garantir que você alcance essa meta para a maioria dos usuários, um bom limite para medir é o 75o percentil dos carregamentos de página, segmentado em dispositivos móveis e computadores.

Bons valores de INP são 200 milissegundos ou menos, valores ruins são maiores do que 500 milissegundos, e tudo o que está entre eles precisa ser melhorado.

Dependendo do site, pode haver poucas ou nenhuma interação, como páginas compostas principalmente de texto e imagens com poucos ou nenhum elemento interativo. Ou, no caso de sites como editores de texto ou jogos, pode haver centenas, ou até milhares, de interações. Nos dois casos, quando há um INP alto, a experiência do usuário está em risco.

Melhorar a INP leva tempo e esforço, mas a recompensa é uma experiência melhor para o usuário. Neste guia, exploraremos um caminho para melhorar o INP.

Descubra o que está causando uma INP ruim

Antes de corrigir interações lentas, você precisará de dados para informar se o INP do seu site é ruim ou precisa de melhorias. Depois de ter essas informações, você pode passar para o laboratório para começar a diagnosticar interações lentas e buscar uma solução.

Encontrar interações lentas no campo

O ideal é que sua jornada na otimização do INP comece com os dados de campo. Na melhor das hipóteses, os dados de campo de um provedor de monitoramento de usuário real (RUM, na sigla em inglês) fornecem não apenas o valor de INP de uma página, mas também dados contextuais que destacam qual interação específica foi responsável pelo próprio valor de INP, se a interação ocorreu durante ou após o carregamento da página, o tipo de interação (clique, pressionamento de tecla ou toque) e outras informações valiosas.

Se você não depende de um provedor de RUM para receber dados de campo, o Guia de dados de campo INP recomenda usar o Chrome User Experience Report (CrUX, na sigla em inglês) pelo PageSpeed Insights para ajudar a preencher as lacunas. O CrUX é o conjunto de dados oficial do programa Core Web Vitals e oferece um resumo de alto nível das métricas de milhões de sites, incluindo a INP. No entanto, o CrUX muitas vezes não fornece os dados contextuais que você recebe de um provedor de RUM para ajudar a analisar problemas. Por isso, ainda recomendamos que os sites usem um provedor de RUM sempre que possível ou implementem a própria solução de RUM para complementar o que está disponível no CrUX.

Diagnosticar interações lentas no laboratório

O ideal é começar os testes no laboratório quando você tiver dados de campo que sugiram interações lentas. Na ausência de dados de campo, há algumas estratégias para identificar interações lentas no laboratório. Essas estratégias incluem seguir fluxos de usuários comuns e testar interações ao longo do caminho, bem como interagir com a página durante o carregamento (quando a linha de execução principal costuma ser mais ocupada) para revelar interações lentas durante essa parte crucial da experiência do usuário.

Otimizar interações

Depois de identificar uma interação lenta e conseguir reproduzi-la manualmente no laboratório, a próxima etapa é otimizá-la. As interações podem ser divididas em três fases:

  1. O atraso de entrada, que começa quando o usuário inicia uma interação com a página e termina quando os callbacks de evento para a interação começam a ser executados.
  2. A duração do processamento, que consiste no tempo que leva para os callbacks de eventos serem executados.
  3. O atraso da apresentação, que é o tempo que o navegador leva para apresentar o próximo frame com o resultado visual da interação.

A soma dessas três fases é a latência total de interação. Cada fase de uma interação contribui para a latência de interação total. Por isso, é importante saber como otimizar cada parte da interação para que ela seja executada pelo menor tempo possível.

Identificar e reduzir o atraso de entrada

Quando um usuário interage com uma página, a primeira parte dessa interação é o atraso de entrada. Dependendo de outra atividade na página, os atrasos de entrada podem ser consideráveis. Isso pode ocorrer devido à atividade que ocorre na linha de execução principal (talvez devido ao carregamento, análise e compilação de scripts), gerenciamento de busca, funções de timer ou até mesmo outras interações que ocorrem em rápida sucessão e se sobrepõem umas às outras.

Seja qual for a origem do atraso de entrada de uma interação, reduza o atraso de entrada ao mínimo, para que as interações possam começar a executar callbacks de eventos o mais rápido possível.

A relação entre a avaliação de script e tarefas longas durante a inicialização

Um aspecto crítico da interatividade no ciclo de vida da página é durante a inicialização. Quando uma página é carregada, ela é renderizada inicialmente. No entanto, é importante lembrar que o fato de uma página ter sido renderizada não significa que o carregamento terminou. Dependendo de quantos recursos uma página precisa para se tornar totalmente funcional, é possível que os usuários tentem interagir com ela durante o carregamento.

A avaliação do script pode aumentar o atraso da entrada de uma interação enquanto uma página carrega. Depois que um arquivo JavaScript é recuperado da rede, o navegador ainda tem trabalho a fazer antes que o JavaScript possa ser executado. Esse trabalho inclui analisar um script para garantir que sua sintaxe seja válida, compilá-lo em bytecode e, em seguida, executá-lo.

Dependendo do tamanho de um script, esse trabalho pode introduzir tarefas longas na linha de execução principal, o que atrasará o navegador de responder a outras interações do usuário. Para manter sua página responsiva à entrada do usuário durante o carregamento, é importante entender o que você pode fazer para reduzir a probabilidade de tarefas longas durante o carregamento para que a página permaneça rápida.

Otimizar callbacks de eventos

O atraso de entrada é apenas a primeira parte do que o INP mede. Você também precisa garantir que os callbacks de evento executados em resposta a uma interação do usuário possam ser concluídos o mais rápido possível.

Use a linha de execução principal com frequência

O melhor conselho geral para otimizar callbacks de eventos é fazer o mínimo possível de trabalho neles. No entanto, sua lógica de interação pode ser complexa, e talvez você só consiga reduzir marginalmente o trabalho que ela faz.

Se você achar que esse é o caso do seu site, a próxima etapa será dividir o trabalho nos callbacks de evento em tarefas separadas. Isso evita que o trabalho coletivo se torne uma tarefa longa que bloqueia a linha de execução principal, o que permite que outras interações que, de outra forma, estariam aguardando a linha de execução principal serem executadas mais cedo.

O setTimeout é uma maneira de dividir tarefas, porque o callback transmitido para ele é executado em uma nova tarefa. Você pode usar o setTimeout sozinho ou abstrair o uso em uma função separada para ter um rendimento mais ergonômico.

Gerar indiscriminadamente é melhor do que não ceder. No entanto, há uma maneira mais diferenciada de ceder à linha de execução principal, e isso envolve apenas produzir imediatamente após um callback de evento que atualiza a interface do usuário para que a lógica de renderização possa ser executada mais rapidamente.

Rendimento para permitir que o trabalho de renderização ocorra mais rapidamente

Uma técnica de rendimento mais avançada envolve estruturar o código nos seus callbacks de eventos para limitar o que é executado apenas à lógica necessária para aplicar atualizações visuais para o próximo frame. Todo o resto pode ser adiado para uma tarefa subsequente. Isso não apenas mantém os callbacks leves e ágeis, mas também melhora o tempo de renderização para interações ao impedir que atualizações visuais sejam bloqueadas no código do callback do evento.

Por exemplo, imagine um editor de rich text que formata o texto enquanto você digita, mas também atualiza outros aspectos da interface do usuário em resposta ao que você escreveu (como contagem de palavras, destaque de erros de ortografia e outros feedbacks visuais importantes). Além disso, o aplicativo também pode precisar salvar o que você escreveu para que, se você sair e voltar, não perca nenhum trabalho.

Neste exemplo, as quatro coisas a seguir precisam acontecer em resposta aos caracteres digitados pelo usuário. No entanto, somente o primeiro item precisa ser feito antes que o próximo frame seja apresentado.

  1. Atualize a caixa de texto com o que o usuário digitou e aplique a formatação necessária.
  2. Atualize a parte da IU que exibe a contagem de palavras atual.
  3. Execute a lógica para verificar se há erros de ortografia.
  4. Salve as alterações mais recentes (localmente ou em um banco de dados remoto).

O código para fazer isso deve ser parecido com este:

textBox.addEventListener('input', (inputEvent) => {
  // Update the UI immediately, so the changes the user made
  // are visible as soon as the next frame is presented.
  updateTextBox(inputEvent);

  // Use `setTimeout` to defer all other work until at least the next
  // frame by queuing a task in a `requestAnimationFrame()` callback.
  requestAnimationFrame(() => {
    setTimeout(() => {
      const text = textBox.textContent;
      updateWordCount(text);
      checkSpelling(text);
      saveChanges(text);
    }, 0);
  });
});

A visualização a seguir mostra como adiar atualizações não críticas para depois do próximo frame pode reduzir a duração do processamento e, portanto, a latência geral da interação.

Representação de uma interação com o teclado e tarefas subsequentes em dois cenários. Na figura superior, a tarefa crítica de renderização e todas as tarefas em segundo plano subsequentes são executadas de forma síncrona até que chegue a oportunidade de apresentar um frame. Na figura inferior, o trabalho crítico de renderização é executado primeiro e depois se rende à linha de execução principal para apresentar um novo frame mais cedo. As tarefas em segundo plano são executadas depois disso.
Clique na figura acima para conferir uma versão em alta resolução.

Embora o uso de setTimeout() dentro de uma chamada requestAnimationFrame() no exemplo de código anterior seja um pouco esotérico, ele é um método eficaz que funciona em todos os navegadores para garantir que o código não crítico não bloqueie o próximo frame.

Evitar a troca frequente de layouts

A troca frequente de layouts, às vezes chamada de layout síncrono forçado, é um problema de desempenho de renderização em que o layout ocorre de forma síncrona. Ela ocorre quando você atualiza estilos em JavaScript e depois os lê na mesma tarefa. Há muitas propriedades no JavaScript que podem causar sobrecarga do layout.

Visualização da troca frequente de layouts, conforme mostrado no painel de desempenho do Chrome DevTools.
Um exemplo de troca frequente de layouts, conforme mostrado no painel de desempenho do Chrome DevTools. As tarefas de renderização que envolvem a troca frequente de layouts serão identificadas com um triângulo vermelho no canto superior direito da parte da pilha de chamadas, geralmente rotulados como Recalcular estilo ou Layout.

A sobrecarga de layout é um gargalo de desempenho porque, ao atualizar estilos e solicitar imediatamente os valores desses estilos no JavaScript, o navegador é forçado a fazer um trabalho de layout síncrono, que, de outra forma, poderia ter esperado por uma execução assíncrona mais tarde, depois que os retornos de chamada do evento terminaram de ser executados.

Minimizar o atraso da apresentação

O atraso da apresentação de uma interação marca o período de execução dos callbacks de evento de uma interação até o momento em que o navegador pode pintar o frame seguinte, que mostra as alterações visuais resultantes.

Minimizar o tamanho do DOM

Quando o DOM de uma página é pequeno, o trabalho de renderização geralmente termina rapidamente. No entanto, quando os DOMs ficam muito grandes, o trabalho de renderização tende a ser escalonado com o aumento do tamanho do DOM. A relação entre o trabalho de renderização e o tamanho do DOM não é linear, mas os DOMs grandes exigem mais trabalho para serem renderizados do que os DOMs pequenos. Um DOM grande é problemático em dois casos:

  1. Durante a renderização inicial da página, em que um DOM grande exige muito trabalho para renderizar o estado inicial.
  2. Em resposta a uma interação do usuário, em que um DOM grande pode fazer com que as atualizações de renderização sejam muito caras e, portanto, aumentem o tempo que o navegador leva para apresentar o próximo frame.

Lembre-se de que há casos em que grandes DOMs não podem ser significativamente reduzidos. Embora existam abordagens que podem ser adotadas para reduzir o tamanho do DOM, como nivelar o DOM ou adicioná-lo durante as interações do usuário para manter o tamanho inicial pequeno, essas técnicas não são tão eficazes.

Usar content-visibility para renderizar lentamente elementos fora da tela

Uma forma de limitar a quantidade de trabalho de renderização durante o carregamento da página e o trabalho de renderização em resposta às interações do usuário é usar a propriedade content-visibility do CSS, que efetivamente equivale a renderizar lentamente elementos à medida que se aproximam da janela de visualização. Embora o uso de content-visibility possa exigir alguma prática para ser usado de maneira eficaz, vale a pena investigar se o resultado é um tempo de renderização menor que pode melhorar o INP da sua página.

Esteja ciente dos custos de desempenho ao renderizar HTML usando JavaScript

Onde há HTML, há análise de HTML e, depois que o navegador termina de analisar HTML em um DOM, ele deve aplicar estilos a ele, realizar cálculos de layout e, em seguida, renderizar esse layout. Esse é um custo inevitável, mas a forma de renderizar HTML é importante.

Quando o servidor envia HTML, ele chega no navegador como um stream. Streaming significa que a resposta HTML do servidor chega em blocos. O navegador otimiza como ele lida com um stream analisando incrementalmente partes desse stream conforme elas chegam e renderizando aos poucos. Essa é uma otimização de desempenho no qual o navegador produz, implicitamente, de forma periódica e automática durante o carregamento da página, e você a recebe sem custo financeiro.

Embora a primeira visita a qualquer site sempre envolva alguma quantidade de HTML, uma abordagem comum começa com um pouco inicial de HTML e, em seguida, usa-se JavaScript para preencher a área do conteúdo. As atualizações subsequentes dessa área de conteúdo também ocorrem como resultado das interações do usuário. Isso geralmente é chamado de modelo de aplicativo de página única (SPA). Uma desvantagem desse padrão é que, ao renderizar o HTML com JavaScript no cliente, você não só obtém o custo do processamento de JavaScript para criá-lo, como também o navegador não se renderá até terminar de analisar esse HTML e renderizá-lo.

No entanto, é vital lembrar que mesmo os sites que não são SPAs provavelmente envolverão uma parte da renderização HTML por JavaScript como resultado das interações. Isso normalmente não é um problema, contanto que você não esteja renderizando grandes quantidades de HTML no cliente, o que pode atrasar a apresentação do próximo frame. No entanto, é importante entender as implicações de desempenho dessa abordagem para renderizar HTML no navegador e como isso pode afetar a capacidade de resposta de seu site à entrada do usuário se você estiver renderizando muito HTML através do JavaScript.

Conclusão

Melhorar o INP do seu site é um processo iterativo. Ao corrigir uma interação lenta no campo, há boas chances de que, principalmente se o site oferece muita interatividade, você começa a encontrar outras interações lentas e precisa otimizá-las também.

A chave para melhorar o INP é a persistência. Com o tempo, você pode fazer com que a capacidade de resposta da página chegue a um ponto em que os usuários estejam satisfeitos com a experiência oferecida. Também são boas chances de que, ao desenvolver novos recursos para os usuários, você precise passar pelo mesmo processo para otimizar interações específicas para eles. Vai levar tempo e esforço, mas é tempo e esforço bem gastos.

Imagem principal do Unsplash, de David Pisnoy (link em inglês), modificada de acordo com a licença do Unsplash.