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

Saiba como otimizar a interação do seu site com a próxima exibição.

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

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

Os valores de INP bons têm 200 milissegundos ou menos, os valores ruins são maiores do que 500 milissegundos e qualquer coisa intermediária precisa ser melhorada.

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 (até milhares) de interações. Nos dois casos, quando há um INP alto, a experiência do usuário fica em risco.

Melhorar o INP exige tempo e esforço, mas a recompensa é uma melhor experiência do usuário. Neste guia, vamos mostrar como melhorar o INP.

Descubra o que está causando uma INP ruim

Antes de corrigir interações lentas, você precisa de dados para informar se o INP do seu site é ruim ou precisa de melhorias. Assim que tiver essas informações, você pode ir para 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 do INP comece com os dados de campo. Na melhor das hipóteses, os dados de campo de um provedor de monitoramento real do usuário (RUM, na sigla em inglês) oferecem 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 (clicar, pressionar ou tocar) e outras informações valiosas.

Se você não depende de um provedor de RUM para acessar os dados de campo, o guia de dados de campo de INP aconselha usar o Chrome User Experience Report (CrUX, na sigla em inglês) via PageSpeed Insights para ajudar a preencher as lacunas. O CrUX é o conjunto de dados oficial do programa Core Web Vitals e oferece um resumo detalhado das métricas de milhões de sites, incluindo o INP. No entanto, o CrUX muitas vezes não oferece os dados contextuais que você conseguiria de um provedor de RUM para ajudar na análise de problemas. Por isso, ainda recomendamos que os sites usem um provedor de RUM sempre que possível ou implementem uma solução RUM própria 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 sugerem 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, além de interagir com a página durante o carregamento (quando a linha de execução principal costuma ser mais movimentada) para identificar 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 do evento da interação começam a ser executados.
  2. O tempo de processamento, que consiste no tempo necessário para que os callbacks de eventos sejam executados até a conclusão.
  3. O atraso da 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 total. Por isso, é importante saber como otimizar cada parte da interação para que ela seja executada o mínimo de tempo possível.

Identificar e reduzir o atraso de entrada

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

Independente da 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 do 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. À medida que uma página é carregada, ela é renderizada inicialmente, mas é importante lembrar que o fato de a página ter sido renderizada não significa que o carregamento 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 enquanto ela ainda está sendo carregada.

A avaliação do script pode aumentar o atraso de entrada de uma interação durante o carregamento de uma página. Depois que um arquivo JavaScript é obtido na rede, o navegador ainda tem o 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, por fim, executá-lo.

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

Otimizar callbacks de eventos

O atraso de entrada é apenas a primeira parte do que o INP mede. Também é necessário garantir que os callbacks de eventos executados em resposta a uma interação do usuário sejam concluídos o mais rápido possível.

Renda-se à 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 marginalmente o trabalho dessas pessoas.

Se você achar que esse é o caso do seu site, tente dividir o trabalho em 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 esperando a linha de execução principal, fossem executadas mais cedo

O setTimeout é uma maneira de dividir as tarefas, porque o callback transmitido para ela é executado em uma nova tarefa. É possível usar setTimeout sozinho ou abstrair o uso em uma função separada para ter um rendimento mais ergonômico.

Propagar indiscriminadamente é melhor do que não se render. No entanto, há uma maneira mais sutil de se ceder à linha de execução principal, que envolve só gerar 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 cedo.

Permita que o trabalho de renderização ocorra mais cedo.

Uma técnica de rendimento mais avançada envolve estruturar o código nos callbacks de eventos para limitar o que é executado apenas à lógica necessária para aplicar atualizações visuais ao 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, não permitindo que atualizações visuais sejam bloqueadas no código de callback do evento.

Por exemplo, imagine um editor de rich text 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 comentários 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 seu 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 da apresentação do próximo frame.

  1. Atualize a caixa de texto com o que o usuário digitou e aplique a formatação necessária.
  2. Atualizar a parte da interface 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 pode ser semelhante ao seguinte:

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 o adiamento de atualizações não críticas para o próximo frame pode reduzir o tempo de processamento e, portanto, a latência geral da interação.

Representação de uma interação com o teclado e de 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 a oportunidade de apresentar um frame tenha chegado. Na figura de baixo, o trabalho crítico de renderização é executado primeiro e, depois, cede à 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 essencial 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. Isso ocorre quando você atualiza estilos em JavaScript e os lê na mesma tarefa. Há muitas propriedades em JavaScript que podem causar uma troca frequente de layouts.

Visualização da troca frequente de layouts, como mostrado no painel de desempenho do Chrome DevTools.
Um exemplo de troca contínua de layout, 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 chamada de Resummarize Style ou Layout.

A sobrecarga de layout é um gargalo de desempenho porque, ao atualizar estilos e, em seguida, solicitar imediatamente os valores desses estilos em JavaScript, o navegador é forçado a realizar um trabalho de layout síncrono que, de outra forma, poderia ter esperado para executar de forma assíncrona mais tarde, depois que os callbacks de eventos terminassem de ser executados.

Minimizar o atraso da apresentação

O atraso da apresentação de uma interação marca o período desde o término da execução dos callbacks de eventos de uma interação até o ponto em que o navegador consegue pintar o próximo frame 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 ampliado 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 de 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.
  2. Em resposta a uma interação do usuário, quando um DOM grande pode fazer com que atualizações de renderização sejam muito caras e, portanto, aumentar o tempo necessário para o navegador apresentar o próximo frame.

Tenha em mente que há casos em que não é possível reduzir significativamente os DOMs. Embora você possa adotar abordagens para reduzir o tamanho do DOM, como nivelar o DOM ou adicionar ao DOM durante as interações do usuário, para manter o tamanho inicial pequeno, essas técnicas são específicas.

Use 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 o trabalho de renderização em resposta às interações do usuário é usar a propriedade CSS content-visibility, que equivale à renderização lenta dos elementos à medida que eles se aproximam da janela de visualização. Embora seja necessário praticar para usar o content-visibility com eficiência, vale a pena investigar se o resultado é um tempo de renderização menor que pode melhorar o INP da sua página.

Preste atenção nos custos de desempenho ao renderizar HTML usando JavaScript

Quando há HTML, há uma 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 subsequentemente renderizar esse layout. Esse é um custo inevitável, mas como a renderização do HTML é importante.

Quando o servidor envia HTML, ele chega ao navegador como um stream. Streaming significa que a resposta HTML do servidor está chegando em partes. O navegador otimiza seu modo de lidar com o stream ao analisar incrementalmente blocos desse stream à medida que eles chegam e processá-los bit a bit. Essa é uma otimização de desempenho na 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 um site sempre envolva alguma quantidade de HTML, uma abordagem comum começa com um pouco inicial de HTML e, em seguida, o JavaScript é usado 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, na sigla em inglês). Uma desvantagem desse padrão é que, ao renderizar HTML com JavaScript no cliente, você não só recebe o custo do processamento do JavaScript para criar esse HTML, mas também o navegador não vai produzir até que termine de analisar e renderizar o HTML.

É importante lembrar, no entanto, que mesmo os sites que não são SPAs provavelmente envolverão um pouco de renderização HTML por meio de JavaScript como resultado das interações. Geralmente, isso não é um problema, 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 desempenho dessa abordagem à renderização de HTML no navegador e como ela pode afetar a capacidade de resposta do seu site à entrada do usuário se você estiver renderizando muito HTML via JavaScript.

Conclusão

Melhorar o INP do seu site é um processo iterativo. Quando você corrige uma interação lenta no campo, há grandes chances de que, especialmente se o site ofereça muita interatividade, você comece a encontrar outras interações lentas e precise otimizá-las também.

A chave para melhorar a INP é a persistência. Com o tempo, você pode fazer com que a capacidade de resposta da página seja atingida de modo que os usuários fiquem satisfeitos com a experiência oferecida. Também é alto o risco de que, à medida que você desenvolver novos recursos para seus usuários, precise passar pelo mesmo processo de otimização das interações específicas para eles. Vai levar tempo e esforço, mas é bem gasto tempo e esforço.

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