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

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

A Interação com a próxima exibição (INP) é uma métrica estável do 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 da visita de um 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.

Valores de INP bons são de 200 milissegundos ou menos, valores ruins são maiores que 500 milissegundos, e qualquer valor entre esses dois extremos precisa ser melhorado.

Dependendo do site, pode haver poucas ou nenhuma interação, como páginas com a maioria 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. Em ambos os casos, quando há um INP alto, a experiência do usuário está em risco.

Melhorar o INP leva tempo e esforço, mas a recompensa é uma experiência do usuário melhor. 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 acessar o laboratório para começar a diagnosticar interações lentas e encontrar uma solução.

Encontrar interações lentas no campo

O ideal é que sua jornada de otimização da INP comece com dados de campo. Na melhor das hipóteses, os dados de campo de um provedor de monitoramento de usuários reais (RUM) vão fornecer 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 valor de INP, se a interação ocorreu durante ou após o carregamento da página, o tipo de interação (clique, tecla ou toque) e outras informações valiosas.

Se você não usa um provedor de RUM para coletar dados de campo, o guia de dados de campo do INP recomenda usar o Chrome User Experience Report (CrUX) pelo PageSpeed Insights para 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 geralmente não fornece os dados contextuais que você receberia 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 ser capaz de reproduzi-la manualmente no laboratório, a próxima etapa é otimizar. 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 eventos para a interação começam a ser executados.
  2. A duração do processamento, que consiste no tempo que os callbacks de eventos levam para serem executados até a conclusão.
  3. O atraso de apresentação, que é o tempo que o navegador leva para apresentar o próximo frame que contém 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 input delay. Dependendo de outras atividades na página, os atrasos de entrada podem ser consideráveis. Isso pode ser devido à atividade que ocorre na linha de execução principal (talvez devido ao carregamento, análise e compilação de scripts), ao processamento de busca, às funções do timer ou até mesmo a outras interações que ocorrem em sucessão rápida e se sobrepõem.

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 essencial da interatividade no ciclo de vida da página é durante a inicialização. À medida que uma página é carregada, ela é renderizada inicialmente, mas é importante lembrar que o fato de uma página ter sido renderizada não significa que o carregamento dela foi concluído. 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.

Uma coisa que pode estender o atraso de entrada de uma interação enquanto uma página é carregada é a avaliação do script. Depois que um arquivo JavaScript é buscado da rede, o navegador ainda precisa fazer algo antes que o JavaScript possa ser executado. Esse trabalho inclui analisar um script para garantir que a sintaxe seja válida, compilar em bytecode e, por fim, executar.

Dependendo do tamanho de um script, esse trabalho pode introduzir tarefas longas na linha de execução principal, o que vai atrasar a resposta do navegador 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 de trabalho possível neles. No entanto, sua lógica de interação pode ser complexa, e talvez você só consiga reduzir ligeiramente o trabalho que ela faz.

Se esse for o caso do seu site, tente dividir o trabalho em callbacks de eventos 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 estariam aguardando a linha de execução principal sejam executadas mais cedo.

O setTimeout é uma maneira de dividir tarefas, porque o callback transmitido para ele é executado em uma nova tarefa. Você pode usar setTimeout sozinho ou abstrair o uso em uma função separada para 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 cedo

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. O restante pode ser adiado para uma tarefa posterior. Isso não apenas mantém os callbacks leves e ágeis, mas também melhora o tempo de renderização das interações, não permitindo que as atualizações visuais bloqueiem o código de callback do evento.

Por exemplo, imagine um editor de texto enriquecido que formata o texto conforme você digita, mas também atualiza outros aspectos da interface 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 interface que mostra a contagem de palavras atual.
  3. Execute a lógica para verificar erros de ortografia.
  4. Salve as alterações mais recentes (localmente ou em um banco de dados remoto).

O código para fazer isso pode ficar mais ou menos assim:

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.

Uma 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.

Evite a sobrecarga de layout

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

Uma visualização do thrashing de layout, mostrada no painel de performance 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 são marcadas com um triângulo vermelho no canto superior direito da parte da pilha de chamadas, geralmente rotulados como Recalcular estilo ou Layout.

A troca frequente de layout é um gargalo de performance porque, ao atualizar estilos e solicitar imediatamente os valores desses estilos em JavaScript, o navegador é forçado a fazer um trabalho de layout síncrono que poderia ter esperado para executar de forma assíncrona mais tarde, depois que os retornos de chamada de eventos tivessem terminado.

Minimizar o atraso da apresentação

O delay de apresentação de uma interação marca o período entre o término da execução dos callbacks de evento de uma interação e o momento em que o navegador consegue pintar o próximo frame que mostra as mudanças 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 DOMs grandes exigem mais trabalho para renderização do que 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 da página.
  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.

Há casos em que DOMs grandes não podem ser reduzidos significativamente. 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 maneira de limitar a quantidade de trabalho de renderização durante o carregamento da página e em resposta às interações do usuário é usar a propriedade CSS content-visibility, que equivale a renderizar elementos de maneira lenta à medida que eles se aproximam da viewport. Embora o uso eficaz de content-visibility exija alguma prática, vale a pena investigar se o resultado é um tempo de renderização menor que pode melhorar a INP da sua página.

Esteja ciente dos custos de desempenho ao renderizar HTML usando JavaScript

Onde há HTML, há análise de HTML. Depois que o navegador termina de analisar o HTML em um DOM, ele precisa aplicar estilos, realizar cálculos de layout e renderizar esse layout. Esse é um custo inevitável, mas a maneira como você renderiza 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 a forma como processa um stream analisando incrementos de pedaços desse stream à medida que eles chegam e renderizando-os bit a bit. Essa é uma otimização de desempenho em que o navegador é renderizado implicitamente de forma periódica e automática durante o carregamento da página, e você recebe isso sem custo financeiro.

Embora a primeira visita a qualquer site sempre envolva alguns elementos HTML, uma abordagem comum começa com um mínimo inicial de HTML e, em seguida, o JavaScript é usado para preencher a área de conteúdo. As atualizações subsequentes nessa á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 HTML com JavaScript no cliente, você não tem apenas o custo do processamento do JavaScript para criar esse HTML, mas também o navegador não renderiza até que ele tenha terminado de analisar esse HTML e renderizá-lo.

No entanto, é importante lembrar que mesmo sites que não são SPAs provavelmente envolvem alguma renderização de HTML usando JavaScript como resultado das interações. Isso geralmente é aceitável, desde que você não renderize 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 performance dessa abordagem para renderizar HTML no navegador e como isso pode afetar a capacidade de resposta do site à entrada do usuário se você estiver renderizando muito HTML usando 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 a INP é a persistência. Com o tempo, você pode deixar a página responsiva para que os usuários fiquem satisfeitos com a experiência que você está oferecendo. Também é provável que, ao desenvolver novos recursos para os usuários, você precise passar pelo mesmo processo de otimização de interações específicas para eles. Isso vai levar tempo e esforço, mas é um 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.