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

Noções básicas sobre a Interaction to Next Paint (INP)

Sobre este codelab

subjectÚltimo jan. 9, 2025 atualizado
account_circleEscrito por Michal Mocny, Brendan Kenny

1. Introdução

Uma demonstração interativa e um codelab para aprender sobre a Interação com a próxima exibição (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 click são executados. Em seguida, 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 interação entre as ações do usuário e o processamento delas afeta 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 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

Extrair 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 Incrementar. Uma demonstração clássica de reatividade e capacidade de resposta.

Captura de tela do app de demonstração deste codelab

Abaixo do botão, há quatro medidas:

  • INP: a pontuação atual de INP, que normalmente é a pior interação.
  • Interação: a pontuação da interação mais recente.
  • QPS: 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 FPS e Timer não são necessárias para medir interações. Elas são adicionadas apenas para facilitar um pouco a visualização da capacidade de resposta.

Faça um teste

Tente interagir com o botão Incrementar e veja a pontuação aumentar. Os valores de INP e Interação mudam a cada incremento?

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

3. Medir interações com o Chrome DevTools

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

Mude para o painel Performance, que você vai usar 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 gravar uma interação usando o painel de performance do DevTools

Duas interações aparecem. Aumente o zoom no segundo rolando ou pressionando a tecla W.

Uma captura de tela do painel "Performance" do DevTools, com 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. Os comprimentos exatos dependem da velocidade da sua máquina.

4. Listeners de eventos de longa duração

Abra o arquivo index.js e remova o comentário da função blockFor no 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 novamente. As interações agora serão visivelmente mais lentas.

Rastreamento de performance

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

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

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

Quando você passa o cursor sobre a interação, percebe que o tempo é gasto quase totalmente 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 no INP.

Atualizar a interface primeiro

O que acontece se você trocar a ordem das chamadas JavaScript: atualizar a interface primeiro e depois bloquear?

Confira o código completo: ui_first.html

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

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

Tente fazer um rastreamento e examine a interação para ver se houve alguma diferença.

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.

Confira o código completo: two_click.html

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

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

Como ele aparece no painel de performance agora?

Diferentes tipos de eventos

A maioria das interações vai acionar muitos tipos de eventos, desde eventos de ponteiro ou tecla até eventos de passar o cursor, foco/desfoque e 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 eventos 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 interface do listener de eventos?

Confira o código completo: no_ui.html

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

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

Rastreamento de performance: primeiro atualize a interface

Confira o código completo: ui_first.html

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

Ao analisar uma gravação do painel "Performance" de um clique no botão, você pode 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 renderizado na tela até que o listener de eventos fosse concluído. Isso significa que a interação ainda levou pouco mais de um segundo para ser concluída.

Uma interação estática de um segundo no painel "Desempenho"

Rastreamento de desempenho: listeners separados

Confira 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ê aumentar o zoom na interação de clique, vai notar que há duas funções diferentes sendo chamadas como resultado do evento click.

Como esperado, a primeira, que atualiza a interface, é executada muito rapidamente, enquanto a segunda leva um segundo inteiro. No entanto, a soma dos efeitos resulta na mesma interação lenta para o usuário final.

Um olhar ampliado na interação de um segundo neste exemplo, mostrando a primeira chamada de função levando menos de um milissegundo para ser concluída

Rastreamento de desempenho: diferentes tipos de eventos

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

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

Esses resultados são muito parecidos. A interação ainda dura um segundo inteiro. A única diferença é que o listener click mais curto, que só atualiza a interface, agora é executado depois do listener pointerup de bloqueio.

Um olhar ampliado na interação de um segundo neste exemplo, mostrando o listener de eventos de clique levando menos de um milissegundo para ser concluído, após o listener pointerup

Rastreamento de desempenho: sem atualização da interface

Confira o código completo: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});
  • A pontuação não é atualizada, mas a página ainda é.
  • 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 fica ativo e volta ao estado anterior quando clicado, o que exige uma renderização do navegador, ou seja, ainda há um 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.

Fazer uma gravação do painel de performance mostra a interação praticamente idêntica às anteriores.

Uma interação estática de um segundo no painel "Desempenho"

Comida para viagem

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ódigo de framework ou biblioteca que é executado em listeners, como uma atualização de estado que aciona uma renderização de componente.
  • Não apenas seu próprio código, mas também todos os scripts de terceiros.

É um problema comum!

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

7. Experiment: input delay

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

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

Tente remover 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();
});

Gravar um clique de botão que ocorre enquanto a tarefa de bloqueio setInterval está em execução resulta em uma interação de longa duração, mesmo sem trabalho de bloqueio sendo feito na própria interação.

Esses períodos de longa duração são 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, e não à duração do processamento.

O painel &quot;Performance&quot; do DevTools mostrando uma tarefa de bloqueio de um segundo, uma interação chegando no meio 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 só causam problemas às vezes.

Uma maneira de rastrear esses problemas é medindo tarefas longas (ou frames de animação longos) e o tempo total de bloqueio.

9. Apresentação lenta

Até agora, analisamos o desempenho do JavaScript usando atraso de entrada ou listeners de eventos. Mas o que mais afeta a renderização do próximo frame?

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á-las.

Na linha de execução principal:

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

Fora da linha de execução principal:

  • Como usar CSS para ativar efeitos de 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 SPA que reconstrói 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 executa listeners caros para isso.
  • Uma alternância do 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 na 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:

Confira 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

Confira o código completo: presentation_delay.html

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

A interação continua durando um segundo. Então, o que aconteceu?

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

Uma interação estática de um segundo no painel &quot;Desempenho&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 "Animation Frame Fired".

12. Diagnosticar interações

Nessa página de teste, a capacidade de resposta é supervisual, com as pontuações, os timers e a interface do contador. No entanto, ao testar a página média, ela é mais sutil.

Quando as interações são longas, nem sempre fica claro qual é o problema. É:

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

Em qualquer página, use o DevTools para medir a capacidade de resposta. Para criar o hábito, siga este fluxo:

  1. Navegue na Web como de costume.
  2. Fique de olho no registro de interações na visualização de métricas dinâmicas do painel "Performance" do DevTools.
  3. Se você notar 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, grave um rastreamento 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 performance para diagnosticar os problemas.

13. Experimento: trabalho assíncrono

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

Desde que a próxima renderização 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 de interação será interrompida.

Para testar isso, continue atualizando a UI do listener de clique, mas execute o trabalho de bloqueio do 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 de um segundo que agora ocorre mais tarde no rastreamento

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 longo ainda é executada, mas algum tempo depois da renderização. Assim, o usuário recebe 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, teríamos removido ele.

Meta:

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

Algumas maneiras de fazer isso:

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

"requestPostAnimationFrame"

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

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

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

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

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

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

Confira 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 interações futuras, além de muitas outras animações e atualizações da página.

Tente novamente a versão de trabalho de bloqueio assíncrono da página (ou a sua própria, se você criou 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?

Rastreamento de performance

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

Várias tarefas de um segundo na linha de execução principal, causando interações lentas 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 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 completamente as tarefas longas.

  • Remova todo o código desnecessário, principalmente scripts.
  • Otimize o código para evitar a execução de tarefas longas.
  • Abortar 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 o processamento ou os efeitos de rede forem caros, atrase o início do trabalho de propósito para poder 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 quando fizer 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);
});

Rastreamento de performance

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

Apesar de vários cliques, apenas uma tarefa blockFor acaba sendo executada, aguardando até que não haja cliques por um segundo inteiro antes de ser executada. Para interações que ocorrem em rajadas, 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 trabalhos de longa duração

Ainda há a chance de um clique adicional ocorrer logo após o período de debounce, cair no meio dessa tarefa longa e se tornar uma interação muito lenta devido ao atraso de entrada.

O ideal é que, se uma interação acontecer no meio da tarefa, pausemos 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.

Confira 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 agende cada tarefa individualmente, e a entrada pode ter maior prioridade.

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

Voltamos a cinco segundos completos 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 se sobrepondo a essas tarefas, nenhuma interação tem um atraso de entrada superior a 100 milissegundos. O navegador prioriza os listeners de eventos recebidos em vez do trabalho setTimeout, e as interações permanecem responsivas.

Essa estratégia funciona muito bem ao programar pontos de entrada separados, como se você tivesse vários recursos independentes que precisasse chamar no momento do carregamento do aplicativo. Apenas carregar scripts e executar tudo no momento da avaliação do script pode executar tudo em uma tarefa longa e gigante por padrão.

No entanto, essa estratégia não funciona tão bem para separar códigos fortemente acoplados, como um loop for que usa estado compartilhado.

Agora com yield()

No entanto, podemos aproveitar o async e o await modernos para adicionar facilmente "pontos de retorno" 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 é gerada após um bloco de trabalho, e o navegador consegue responder a todas as interações recebidas. 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 remoção de duplicação, cancelamos o tempo limite anterior a cada nova interação. Podemos fazer algo parecido aqui? Uma maneira de fazer isso é usar um AbortController():

Confira 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 chega, ele inicia o loop blockInPiecesYieldyAborty for, fazendo o trabalho necessário e cedendo periodicamente a linha de execução principal para que o navegador permaneça responsivo a novas interações.

Quando um segundo clique chega, 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 retornará imediatamente sem fazer mais nada.

O trabalho da linha de execução principal agora está em muitas 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ê dê feedback inicial rapidamente e tome decisões, como interromper o trabalho em andamento. Às vezes, isso significa programar pontos de entrada como tarefas separadas. Às vezes, isso significa adicionar pontos de "rendimento" quando for conveniente.

Lembrete

  • A INP mede todas as interações.
  • Cada interação é medida da entrada até a próxima exibição, ou seja, a maneira como o usuário a capacidade de resposta.
  • A latência na entrada, a duração do processamento de eventos e a latência na apresentação afetam a capacidade de resposta à interação.
  • Você pode medir o INP e os detalhamentos de interação com o DevTools com facilidade.

Estratégias

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

Saiba mais