Noções básicas sobre a interação com a Next Paint (INP)

1. Introdução

Uma demonstração interativa e um codelab para Aprenda Mais sobre a Interaction to Next Paint (INP).

Um diagrama que mostra uma interação na linha de execução principal. O usuário faz uma entrada enquanto as tarefas de bloqueio são executadas. A entrada é atrasada até que essas tarefas sejam concluídas. Depois disso, os listeners de eventos pointerup, mouseup e evento de clique são executados, e o trabalho de renderização e pintura é iniciado até que o próximo frame seja apresentado.

Pré-requisitos

Conteúdo do laboratório

  • Como a combinação das interações do usuário e o processamento delas afetam a capacidade de resposta da página.
  • Como reduzir e eliminar atrasos para uma experiência do usuário tranquila.

O que é necessário

  • Um computador com a capacidade de clonar código do GitHub e executar comandos npm.
  • Um editor de texto.
  • Uma versão recente do Chrome para que todas as medições de interação funcionem.

2. Começar a configuração

Acessar e executar o código

O código está no repositório web-vitals-codelabs.

  1. Clone o repositório no terminal: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Acesse o diretório clonado: cd web-vitals-codelabs/understanding-inp
  3. Instale as dependências: npm ci
  4. Inicie o servidor da Web: npm run start
  5. Acesse http://localhost:5173/understanding-inp/ no navegador.

Visão geral do app

Na parte de cima da página, há um contador de pontuação e um botão Increment. Uma demonstração clássica de reatividade e responsividade.

Uma captura de tela do app de demonstração deste codelab

Abaixo do botão, há quatro medições:

  • INP: a pontuação atual de INP, que normalmente é a pior interação.
  • Interação: a pontuação da interação mais recente.
  • FPS: os quadros por segundo da linha de execução principal da página.
  • Timer: uma animação de timer em execução para ajudar a visualizar o jank.

As entradas de QPS e Timer não são necessárias para medir interações. Eles são adicionados apenas para facilitar a visualização da responsividade.

Faça um teste

Tente interagir com o botão Increment e observe o aumento da pontuação. Os valores de INP e Interação mudam a cada incremento?

A INP mede o tempo que leva desde o momento em que o usuário interage até que a página mostre a atualização renderizada para o usuário.

3. Como medir interações com o Chrome DevTools

Abra o DevTools no menu Mais ferramentas > Ferramentas para desenvolvedores, clicando com o botão direito do mouse na página e selecionando Inspecionar ou usando um atalho do teclado.

Mude para o painel Performance, que será usado para medir as interações.

Captura de tela do painel "Performance" do DevTools ao lado do app

Em seguida, capture uma interação no painel "Performance".

  1. pressione "Gravar".
  2. Interaja com a página (pressione o botão Increment).
  3. Interrompa a gravação.

Na linha do tempo resultante, você vai encontrar uma faixa de Interações. Clique no triângulo à esquerda para expandir.

Uma demonstração animada de como registrar uma interação usando o painel de desempenho do DevTools

Duas interações aparecem. Aumente o zoom na segunda, rolando a tela ou mantendo a tecla W pressionada.

Uma captura de tela do painel "Performance" do DevTools, o cursor passando sobre a interação no painel e uma dica mostrando o tempo curto da interação

Ao passar o cursor sobre a interação, você pode ver que ela foi rápida, sem gastar tempo na duração do processamento e com um tempo mínimo de atraso de entrada e atraso de apresentação, cujas durações exatas dependem da velocidade da sua máquina.

4. Listeners de eventos de longa duração

Abra o arquivo index.js e remova a marca de comentário da função blockFor dentro do listener de eventos.

Confira o código completo: click_block.html

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();
});

Salve o arquivo. O servidor vai detectar a mudança e atualizar a página para você.

Tente interagir com a página de novo. As interações agora serão visivelmente mais lentas.

Trace de performance

Faça outra gravação no painel "Performance" para ver como isso aparece lá.

Uma interação de um segundo no painel "Performance"

O que antes era uma interação curta agora leva um segundo inteiro.

Ao passar o cursor sobre a interação, observe que o tempo é quase todo gasto em "Duração do processamento", que é o tempo necessário para executar os callbacks do listener de eventos. Como a chamada de bloqueio blockFor está totalmente dentro do listener de eventos, é aí que o tempo é gasto.

5. Experimento: duração do processamento

Teste maneiras de reorganizar o trabalho do listener de eventos para ver o efeito na INP.

Atualizar a interface primeiro

O que acontece se você trocar a ordem das chamadas JS, atualizando primeiro a interface e depois bloqueando?

Confira o código completo: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

Você percebeu que a interface apareceu antes? A ordem afeta as pontuações de INP?

Tente fazer um trace e examinar a interação para ver se há diferenças.

Listeners separados

E se você mover o trabalho para um listener de eventos separado? Atualize a interface em um listener de eventos e bloqueie a página em um listener separado.

Veja o código completo: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

Como ele aparece no painel de desempenho agora?

Tipos de evento diferentes

A maioria das interações aciona muitos tipos de eventos, desde eventos de ponteiro ou de tecla até eventos de passagem, foco/desfoque e eventos sintéticos, como beforechange e beforeinput.

Muitas páginas reais têm listeners para vários eventos diferentes.

O que acontece se você mudar os tipos de evento para os listeners de eventos? Por exemplo, substituir um dos listeners de eventos click por pointerup ou mouseup?

Confira o código completo: diff_handlers.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Nenhuma atualização da interface

O que acontece se você remover a chamada para atualizar a IU do listener de eventos?

Veja o código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});

6. Resultados do experimento de duração do processamento

Trace de desempenho: atualizar a interface primeiro

Confira o código completo: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

Ao analisar uma gravação do painel "Desempenho" do clique no botão, é possível notar que os resultados não mudaram. Embora uma atualização da interface tenha sido acionada antes do código de bloqueio, o navegador não atualizou o que foi pintado na tela até que o listener de eventos fosse concluído, o que significa que a interação ainda levou pouco mais de um segundo para ser concluída.

Uma interação de um segundo no painel "Performance"

Trace de desempenho: listeners separados

Veja o código completo: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

Não há diferença funcional. A interação ainda leva um segundo inteiro.

Se você ampliar a interação de clique, vai notar que há duas funções diferentes sendo chamadas como resultado do evento click.

Como esperado, a primeira (atualização da interface) é executada de forma incrivelmente rápida, enquanto a segunda leva um segundo inteiro. No entanto, a soma dos efeitos resulta na mesma interação lenta para o usuário final.

Uma visão ampliada da interação de um segundo neste exemplo, mostrando que a primeira chamada de função leva menos de um milissegundo para ser concluída

Trace de desempenho: diferentes tipos de evento

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

Esses resultados são muito semelhantes. A interação ainda leva um segundo inteiro. A única diferença é que o listener click, que é mais curto e atualiza apenas a IU, agora é executado depois do listener pointerup, que é bloqueante.

Uma visão ampliada da interação de um segundo neste exemplo, mostrando que o listener de eventos de clique leva menos de um milissegundo para ser concluído, após o listener pointerup

Trace de desempenho: sem atualização da interface

Veja o código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • A pontuação não é atualizada, mas a página sim.
  • Animações, efeitos CSS, ações padrão de componentes da Web (entrada de formulário), entrada de texto e destaque de texto continuam sendo atualizados.

Nesse caso, o botão passa para um estado ativo e volta quando clicado, o que exige uma pintura pelo navegador, ou seja, ainda há uma INP.

Como o listener de eventos bloqueou a linha de execução principal por um segundo, impedindo que a página fosse renderizada, a interação ainda leva um segundo inteiro.

Uma gravação do painel "Performance" mostra que a interação é praticamente idêntica às anteriores.

Uma interação de um segundo no painel "Performance"

Takeaway

Qualquer código em execução em qualquer listener de eventos vai atrasar a interação.

  • Isso inclui listeners registrados de diferentes scripts e códigos de framework ou biblioteca que são executados em listeners, como uma atualização de estado que aciona uma renderização de componente.
  • Não apenas o seu código, mas também todos os scripts de terceiros.

É um problema comum.

Por fim, só porque seu código não aciona uma pintura, não significa que uma pintura não vai esperar que os listeners de eventos lentos sejam concluídos.

7. Experimento: latência na entrada

E quanto ao código de longa duração fora dos listeners de eventos? Exemplo:

  • Se você tiver um <script> de carregamento tardio que bloqueia a página aleatoriamente durante o carregamento.
  • Uma chamada de API, como setInterval, que bloqueia periodicamente a página?

Remova o blockFor do listener de eventos e adicione-o a um setInterval():

Confira o código completo: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

O que acontece?

8. Resultados do experimento de latência na entrada

Confira o código completo: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

Registrar um clique em um botão que ocorre enquanto a tarefa de bloqueio setInterval está em execução resulta em uma interação de longa duração, mesmo que nenhum trabalho de bloqueio esteja sendo feito na interação em si.

Esses períodos de longa duração costumam ser chamados de tarefas longas.

Ao passar o cursor sobre a interação no DevTools, você vai notar que o tempo de interação agora é atribuído principalmente ao atraso de entrada, não à duração do processamento.

O painel &quot;Performance&quot; do DevTools mostrando uma tarefa de bloqueio de um segundo, uma interação que ocorre em parte dessa tarefa e uma interação de 642 milissegundos, atribuída principalmente ao atraso de entrada

Observe que isso nem sempre afeta as interações. Se você não clicar enquanto a tarefa estiver em execução, talvez tenha sorte. Esses espirros "aleatórios" podem ser um pesadelo para depurar quando causam problemas apenas algumas vezes.

Uma maneira de rastrear esses problemas é medir tarefas longas (ou frames longos de animação) e o Total Blocking Time.

9. Apresentação lenta

Até agora, analisamos o desempenho do JavaScript, por meio de atraso de entrada ou listeners de eventos, mas o que mais afeta a INP?

Atualizando a página com efeitos caros.

Mesmo que a atualização da página seja rápida, o navegador ainda pode ter que trabalhar muito para renderizá-la.

Na linha de execução principal:

  • Frameworks de interface que precisam renderizar atualizações após mudanças de estado
  • Mudanças no DOM ou a alternância de muitos seletores de consulta CSS caros podem acionar muitos estilos, layouts e pinturas.

Fora da linha de execução principal:

  • Como usar CSS para potencializar os efeitos da GPU
  • Adicionar imagens muito grandes em alta resolução
  • Como usar SVG/Canvas para desenhar cenas complexas

Esboço dos diferentes elementos de renderização na Web

RenderingNG

Alguns exemplos comuns encontrados na Web:

  • Um site de SPA que recria todo o DOM depois de clicar em um link, sem pausar para fornecer um feedback visual inicial.
  • Uma página de pesquisa que oferece filtros de pesquisa complexos com uma interface do usuário dinâmica, mas que executa listeners caros para fazer isso.
  • Um botão de modo escuro que aciona o estilo/layout de toda a página

10. Experimento: atraso na apresentação

requestAnimationFrame lento

Vamos simular um longo atraso de apresentação usando a API requestAnimationFrame().

Mova a chamada blockFor para um callback requestAnimationFrame para que ela seja executada depois que o listener de eventos retornar:

Veja o código completo: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

O que acontece?

11. Resultados do experimento de atraso na apresentação

Veja o código completo: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

A interação continua com um segundo de duração, então o que aconteceu?

requestAnimationFrame solicita um callback antes da próxima pintura. Como a INP mede o tempo entre a interação e a próxima pintura, o blockFor(1000) no requestAnimationFrame continua bloqueando a próxima pintura por um segundo inteiro.

Uma interação de um segundo no painel &quot;Performance&quot;

No entanto, observe duas coisas:

  • Ao passar o cursor, você vai notar que todo o tempo de interação agora está sendo gasto em "atraso de apresentação", já que o bloqueio da linha de execução principal está acontecendo depois que o listener de eventos retorna.
  • A raiz da atividade da linha de execução principal não é mais o evento de clique, mas sim "Animation Frame Fired".

12. Como diagnosticar interações

Nesta página de teste, a responsividade é super visual, com as pontuações, os timers e a interface do contador, mas ao testar a página média, ela é mais sutil.

Quando as interações demoram, nem sempre fica claro qual é o culpado. É:

  • Latência na entrada?
  • Duração do processamento de eventos?
  • Atraso na apresentação?

Em qualquer página, você pode usar o DevTools para medir a responsividade. Para criar o hábito, siga este fluxo:

  1. Navegue na Web normalmente.
  2. Fique de olho no registro de interações na visualização de métricas em tempo real do painel "Performance" do DevTools.
  3. Se você encontrar uma interação com desempenho ruim, tente repeti-la:
  • Se não for possível repetir, use o registro de interações para receber insights.
  • Se for possível repetir, registre um rastro no painel "Performance".

Todos os atrasos

Tente adicionar um pouco de todos esses problemas à página:

Confira o código completo: all_the_things.html

setInterval(() => {
  blockFor(1000);
}, 3000);

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

Em seguida, use o console e o painel de desempenho para diagnosticar os problemas.

13. Experimento: trabalho assíncrono

Como é possível iniciar efeitos não visuais dentro de interações, como fazer solicitações de rede, iniciar timers ou apenas atualizar o estado global, o que acontece quando eles eventualmente atualizam a página?

Desde que a próxima pintura após uma interação possa ser renderizada, mesmo que o navegador decida que não precisa de uma nova atualização de renderização, a medição da interação é interrompida.

Para testar isso, continue atualizando a IU no listener de cliques, mas execute o trabalho de bloqueio no tempo limite.

Confira o código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

O que acontece agora?

14. Resultados do experimento de trabalho assíncrono

Confira o código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

Uma interação de 27 milissegundos com uma tarefa longa de um segundo que agora ocorre mais tarde no trace

A interação agora é curta porque a linha de execução principal fica disponível imediatamente após a atualização da interface. A tarefa de bloqueio longa ainda é executada, mas só depois da exibição, para que o usuário receba feedback imediato da interface.

Lição: se não for possível remover, pelo menos mova.

Métodos

Podemos fazer melhor do que um setTimeout fixo de 100 milissegundos? Provavelmente, ainda queremos que o código seja executado o mais rápido possível. Caso contrário, bastaria removê-lo.

Meta:

  • A interação vai executar incrementAndUpdateUI().
  • blockFor() será executado assim que possível, mas não vai bloquear a próxima pintura.
  • Isso resulta em um comportamento previsível sem "tempos limite mágicos".

Algumas maneiras de fazer isso envolvem:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

"requestPostAnimationFrame"

Ao contrário de requestAnimationFrame por conta própria (que tenta ser executado antes da próxima pintura e geralmente ainda resulta em uma interação lenta), requestAnimationFrame + setTimeout cria um polyfill simples para requestPostAnimationFrame, executando o callback depois da próxima pintura.

Veja o código completo: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame(() => {
    setTimeout(callback, 0);
  });
}

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  afterNextPaint(() => {
    blockFor(1000);
  });
});

Para facilitar, você pode até mesmo envolvê-lo em uma promessa:

Veja o código completo: raf+task2.html

async function nextPaint() {
  return new Promise(resolve => afterNextPaint(resolve));
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  await nextPaint();
  blockFor(1000);
});

15. Várias interações (e cliques de raiva)

Mover o trabalho de bloqueio longo pode ajudar, mas essas tarefas longas ainda bloqueiam a página, afetando as interações futuras, bem como muitas outras animações e atualizações da página.

Teste a versão da página com trabalho de bloqueio assíncrono novamente (ou a sua própria, se você tiver criado uma variação para adiar o trabalho na última etapa):

Confira o código completo: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

O que acontece se você clicar várias vezes rapidamente?

Trace de performance

Para cada clique, há uma tarefa de um segundo na fila, garantindo que a linha de execução principal seja bloqueada por um período considerável.

Várias tarefas longas na linha de execução principal, causando interações de até 800 ms

Quando essas tarefas longas se sobrepõem a novos cliques, as interações ficam lentas, mesmo que o listener de eventos em si retorne quase imediatamente. Criamos a mesma situação do experimento anterior com atrasos de entrada. Só que, desta vez, o atraso de entrada não vem de um setInterval, mas de um trabalho acionado por listeners de eventos anteriores.

Estratégias

O ideal é remover as tarefas longas por completo.

  • Remova totalmente o código desnecessário, especialmente os scripts.
  • Otimize o código para evitar a execução de tarefas longas.
  • Interrompa o trabalho desatualizado quando novas interações chegarem.

16. Estratégia 1: debounce

Uma estratégia clássica. Sempre que as interações chegarem em rápida sucessão e os efeitos de processamento ou de rede forem caros, atrase o início do trabalho de propósito para que você possa cancelar e reiniciar. Esse padrão é útil para interfaces do usuário, como campos de preenchimento automático.

  • Use setTimeout para atrasar o início de um trabalho caro com um timer, talvez de 500 a 1.000 milissegundos.
  • Salve o ID do timer ao fazer isso.
  • Se uma nova interação chegar, cancele o timer anterior usando clearTimeout.

Confira o código completo: debounce.html

let timer;
button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(() => {
    blockFor(1000);
  }, 1000);
});

Trace de performance

Várias interações, mas apenas uma tarefa longa como resultado de todas elas

Apesar de vários cliques, apenas uma tarefa blockFor é executada, aguardando até que não haja cliques por um segundo inteiro antes de ser executada. Para interações que ocorrem em sequência, como digitar em uma entrada de texto ou itens que devem receber vários cliques rápidos, essa é uma estratégia ideal para usar por padrão.

17. Estratégia 2: interromper o trabalho de longa duração

Ainda há uma chance de que um clique ocorra logo após o período de debounce, caia no meio dessa tarefa longa e se torne uma interação muito lenta devido ao atraso de entrada.

O ideal é que, se uma interação ocorrer no meio da nossa tarefa, possamos pausar o trabalho para que as novas interações sejam processadas imediatamente. Como podemos fazer isso?

Existem algumas APIs como isInputPending, mas geralmente é melhor dividir tarefas longas em partes.

Muitos setTimeouts

Primeira tentativa: faça algo simples.

Veja o código completo: small_tasks.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
  });
});

Isso funciona permitindo que o navegador programe cada tarefa individualmente, e a entrada pode ter prioridade mais alta.

Várias interações, mas todo o trabalho programado foi dividido em muitas tarefas menores

Voltamos a ter cinco segundos de trabalho para cinco cliques, mas cada tarefa de um segundo por clique foi dividida em dez tarefas de 100 milissegundos. Como resultado, mesmo com várias interações sobrepostas a essas tarefas, nenhuma interação tem um atraso de entrada de mais de 100 milissegundos. O navegador prioriza os listeners de eventos recebidos em relação ao trabalho de setTimeout, e as interações continuam responsivas.

Essa estratégia funciona muito bem ao programar pontos de entrada separados, como quando você tem vários recursos independentes que precisam ser chamados no momento do carregamento do aplicativo. Carregar scripts e executar tudo no momento da avaliação do script pode executar tudo em uma tarefa longa gigante por padrão.

No entanto, essa estratégia não funciona tão bem para separar códigos com acoplamento rígido, como um loop for que usa estado compartilhado.

Agora com yield()

No entanto, podemos usar os modernos async e await para adicionar facilmente "pontos de rendimento" a qualquer função JavaScript.

Exemplo:

Confira o código completo: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

Como antes, a linha de execução principal é liberada após um bloco de trabalho, e o navegador pode responder a qualquer interação recebida. No entanto, agora tudo o que é necessário é um await schedulerDotYield() em vez de setTimeouts separados, o que o torna ergonômico o suficiente para ser usado até mesmo no meio de um loop for.

Agora com AbortContoller()

Isso funcionou, mas cada interação programa mais trabalho, mesmo que novas interações tenham chegado e possam ter mudado o trabalho que precisa ser feito.

Com a estratégia de debouncing, cancelamos o tempo limite anterior a cada nova interação. Podemos fazer algo semelhante aqui? Uma maneira de fazer isso é usar um AbortController():

Veja o código completo: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

Quando um clique é recebido, ele inicia o loop blockInPiecesYieldyAborty for, que faz o trabalho necessário e, ao mesmo tempo, libera periodicamente a linha de execução principal para que o navegador continue responsivo a novas interações.

Quando um segundo clique é recebido, o primeiro loop é sinalizado como cancelado com o AbortController, e um novo loop blockInPiecesYieldyAborty é iniciado. Na próxima vez que o primeiro loop for programado para ser executado novamente, ele vai notar que signal.aborted agora é true e vai retornar imediatamente sem fazer mais nada.

O trabalho da linha de execução principal agora está dividido em várias partes pequenas, as interações são curtas e o trabalho dura apenas o tempo necessário

18. Conclusão

Dividir todas as tarefas longas permite que um site responda a novas interações. Isso permite que você forneça feedback inicial rapidamente e tome decisões, como cancelar o trabalho em andamento. Às vezes, isso significa programar pontos de entrada como tarefas separadas. Às vezes, isso significa adicionar pontos de "rendimento" onde for conveniente.

Vale lembrar

  • A INP mede todas as interações.
  • Cada interação é medida da entrada até a próxima exibição, que é como o usuário percebe a capacidade de resposta.
  • A latência na entrada, a duração do processamento de eventos e o atraso na apresentação afetam a capacidade de resposta da interação.
  • É fácil medir a INP e as interações com o DevTools.

Estratégias

  • Não use códigos de longa duração (tarefas longas) nas suas páginas.
  • Mova o código desnecessário para fora dos listeners de eventos até depois da próxima pintura.
  • Verifique se a atualização de renderização é eficiente para o navegador.

Saiba mais