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

1. Introdução

Uma demonstração interativa e um codelab para aprender sobre Interação com a Next Paint (INP).

Um diagrama que representa uma interação na linha de execução principal. O usuário faz uma entrada ao bloquear a execução de tarefas. A entrada é atrasada até que essas tarefas sejam concluídas. Depois disso, os listeners de eventos de mouseup, mouseup e clique 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 das interações do usuário e seu tratamento dessas interações afetam a capacidade de resposta da página.
  • Como reduzir e eliminar atrasos para proporcionar uma experiência tranquila ao usuário.

O que é necessário

  • Um computador com a capacidade de clonar o 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

Receber e executar o código

O código pode ser encontrado no repositório web-vitals-codelabs.

  1. Clone o repositório no seu terminal: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Acesse o diretório clonado: cd web-vitals-codelabs/understanding-inp
  3. Instalar 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

No topo da página, estão o contador Pontuação e o botão Incrementar. Uma demonstração clássica de reatividade e capacidade de resposta!

Uma captura de tela do app de demonstração para este codelab

Abaixo do botão, há quatro medidas:

  • INP: a pontuação INP atual, que normalmente é a pior interação.
  • Interação: a pontuação da interação mais recente.
  • QPS: os principais frames por segundo da linha de execução da página.
  • Cronômetro: uma animação de cronômetro em execução para ajudar a visualizar a instabilidade.

As entradas de QPS e cronômetro não são necessárias para medir interações. Elas foram adicionadas apenas para facilitar 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 INP e Interaction mudam a cada incremento?

O INP mede quanto tempo leva do momento em que o usuário interage até que a página realmente mostre a atualização renderizada ao usuário.

3. Medir as interações com o Chrome DevTools

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

Alterne para o painel Desempenho, que você usará para medir as interações.

Captura de tela do painel "Performance" do DevTools com o app

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

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

Na linha do tempo resultante, você vai encontrar a faixa Interactions. Clique no triângulo do lado esquerdo para expandi-lo.

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

Duas interações são exibidas. Aumente o zoom no segundo rolando ou segurando a tecla W.

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

Passe o cursor sobre a interação e observe que ela foi rápida, sem gastar tempo na duração do processamento e de uma quantidade mínima de tempo em atraso de entrada e atraso da apresentação. A duração exata depende da velocidade da 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.

Veja o código completo: click_block.html

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

Salve o arquivo. O servidor verá a alteração e atualizará a página para você.

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

Rastreamento de desempenho

Faça outra gravação no painel "Desempenho" para ver o resultado.

Uma interação de um segundo no Painel de desempenho

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

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

5. Experimento: duração do processamento

Tente 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 js: atualizar a interface primeiro e depois bloquear?

Veja o código completo: ui_first.html

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

Você notou que a interface aparece mais cedo? A ordem afeta as pontuações de INP?

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

Listeners separados

E se você mover o trabalho para um listener de eventos separado? Atualizar a IU em um listener de eventos e bloquear a página de um listener separado.

Consulte o código completo: two_click.html

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

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

Como ele aparece no painel de desempenho?

Diferentes tipos de evento

A maioria das interações vai disparar vários tipos de eventos, desde eventos de ponteiro ou teclas até passar o cursor, focar/desfocar e eventos sintéticos, como beforechange e beforeinput.

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

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

Consulte o código completo: diff_handlers.html

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

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

Nenhuma atualização de interface

O que acontece se você remover a chamada para atualizar a interface 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

Rastreamento de desempenho: atualize a interface primeiro

Veja o código completo: ui_first.html

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

Observando uma gravação do Painel de desempenho de clicar no botão, você pode ver que os resultados não mudaram. Embora uma atualização de IU 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 de desempenho.

Rastreamento de desempenho: listeners separados

Consulte o código completo: two_click.html

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

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

Novamente, 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á de fato duas funções diferentes sendo chamadas como resultado do evento click.

Como esperado, a primeira (atualização da interface) é executada incrivelmente rápido, enquanto a segunda leva um segundo. 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 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 semelhantes. A interação ainda dura um segundo inteiro; A única diferença é que o listener click mais curto somente atualização da interface agora é executado após o listener pointerup de bloqueio.

Uma visão ampliada da interação de um segundo de um segundo, mostrando o listener de eventos de clique levando menos de um milissegundo para ser concluído, após o listener ponteiroup.

Rastreamento de desempenho: sem atualização de 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 é atualizada.
  • Animações, efeitos de CSS, ações padrão do componente da Web (entrada de formulário), entrada de texto e destaque de texto continuam sendo atualizados.

Nesse caso, o botão vai para um estado ativo e volta quando clicado, o que exige uma pintura do navegador, o que significa que ainda há um INP.

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

Fazer uma gravação do Painel de desempenho mostra a interação praticamente idêntica à anterior.

Uma interação de um segundo no Painel de desempenho.

Para viagem

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

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

Isso é um problema comum.

Por fim, o fato de o código não acionar uma pintura não significa que ela não estará esperando que os listeners de eventos lentos sejam concluídos.

7. Experimento: atraso de entrada

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

  • Se você tinha um <script> de carregamento atrasado que bloqueou 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 adicioná-lo a um setInterval():

Consulte o código completo: input_delay.html

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


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

O que acontece?

8. Resultados do experimento de atraso de entrada

Consulte o código completo: input_delay.html

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


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

A gravação de 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 que nenhum trabalho de bloqueio seja feito na própria interação.

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

Passe o cursor sobre a interação no DevTools para ver que o tempo de interação agora é atribuído principalmente ao atraso de entrada, não à duração do processamento.

O painel DevTools Performance mostrando uma tarefa de bloqueio de um segundo, uma interação que vem parcialmente por essa tarefa e uma interação de 642 milissegundos, principalmente atribuída a um atraso de entrada.

Observe que isso nem sempre afeta as interações. Se você não clicar quando a tarefa estiver em execução, talvez tenha sorte. Tão "aleatório" espirros pode ser um pesadelo de depuração quando só às vezes causam problemas.

Uma maneira de rastreá-los é medindo tarefas longas (ou frames de animação longos) e tempo total de bloqueio.

9. Apresentação lenta

Até agora, analisamos o desempenho do JavaScript por meio de atrasos de entrada ou listeners de eventos. No entanto, o que mais afeta a renderização da próxima pintura?

Bem, atualizando a página com efeitos caros!

Mesmo que a atualização da página seja feita rapidamente, o navegador pode ter que se esforçar 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 vários estilos, layouts e pinturas.

Fora da linha de execução principal:

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

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

RenderingNG

Alguns exemplos comumente encontrados na Web:

  • Um site de SPA que recria todo o DOM após 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 de usuário dinâmica, mas executa listeners caros para isso.
  • Um botão para ativar/desativar o modo escuro que aciona estilo/layout para 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:

Consulte o código completo: presentation_delay.html

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

O que acontece?

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

Consulte o código completo: presentation_delay.html

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

A interação permanece 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 da interação até 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 de desempenho.

No entanto, observe duas coisas:

  • Ao passar o cursor, você verá que todo o tempo de interação está sendo gasto em "atraso da 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 "Frame de animação disparado".

12. Como diagnosticar interações

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

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

  • Atraso na entrada?
  • Duração do processamento do evento?
  • Atraso na apresentação?

Em qualquer página que quiser, você pode usar o DevTools para ajudar a medir a capacidade de resposta. Para criar o hábito, siga este fluxo:

  1. Navegue na Web normalmente.
  2. Opcional: deixe o console do DevTools aberto enquanto a extensão das Core Web Vitals registra as interações.
  3. Se você encontrar uma interação com baixo desempenho, tente repeti-la:
  • Se não for possível repetir a ação, use os registros do console para receber insights.
  • Se puder repetir, grave no painel de desempenho.

Todos os atrasos

Tente adicionar alguns destes problemas à página:

Veja o código completo: all_the_Things.html

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

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

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

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

13. Experimento: trabalho assíncrono

Já que é 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 atualizam a página depois?

Enquanto a next paint após uma interação puder ser renderizada, mesmo que o navegador decida que não precisa de uma nova atualização de renderização, a medição de interações será interrompida.

Para testar isso, continue atualizando a interface pelo listener de clique, mas execute o trabalho de bloqueio até o tempo limite.

Consulte o código completo: tempo limite_100.html

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

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

O que acontecerá agora?

14. Resultados de experimentos de trabalho assíncronos

Consulte o código completo: tempo limite_100.html

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

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

Uma interação de 27 milissegundos com uma tarefa de um segundo de duração agora ocorrendo mais tarde no rastro

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. Ela apenas é executada algum tempo após a pintura, de modo que o usuário recebe feedback imediato da interface.

Lição: se não puder removê-lo, pelo menos mova-o!

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, ele deve ter sido removido.

Meta:

  • A interação executará incrementAndUpdateUI().
  • blockFor() será executado o mais rápido possível, mas não 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()

&quot;requestPostAnimationFrame&quot;

Ao contrário de requestAnimationFrame sozinho, que tentará ser executado antes da próxima pintura e geralmente ainda terá uma interação lenta, requestAnimationFrame + setTimeout criam um polyfill simples para requestPostAnimationFrame, executando o callback após a próxima pintura.

Consulte 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 incluir uma promessa:

Consulte 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 uma solução longa de bloqueio pode ajudar, mas essas tarefas longas ainda bloqueiam a página, afetando interações futuras, assim como muitas outras animações e atualizações da página.

Teste a versão de trabalho de bloqueio assíncrono da página novamente (ou a sua, se você tiver uma variação sobre o adiamento do trabalho na última etapa):

Consulte o código completo: tempo limite_100.html

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

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

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

Rastreamento de desempenho

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

Várias tarefas de segundos na linha de execução principal, fazendo com que as interações fiquem lentas, como 800 ms

Quando essas tarefas longas se sobrepõem à chegada de novos cliques, isso resulta em interações lentas, mesmo que o próprio listener de eventos retorne quase imediatamente. Criamos a mesma situação do experimento anterior com atrasos de entrada. Apenas desta vez, o atraso de entrada não está vindo de um setInterval, mas sim do trabalho acionado por listeners de eventos anteriores.

Estratégias

O ideal é remover completamente as tarefas longas.

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

16. Estratégia 1: debounce

Uma estratégia clássica. Sempre que as interações ocorrerem 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 trabalhos caros, com um timer de 500 a 1.000 milissegundos.
  • Quando fizer isso, salve o ID do timer.
  • Se uma nova interação chegar, cancele o timer anterior usando clearTimeout.

Consulte o código completo: debounce.html

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

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

Rastreamento de desempenho

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

Apesar dos vários cliques, apenas uma tarefa blockFor acaba em execução, aguardando até que não haja nenhum clique por um segundo antes de ser executada. Para interações que ocorrem em sequência, como digitar uma entrada de texto ou itens de destino 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á a chance azarada de que um novo clique ocorra logo após o período de debounce, cai no meio dessa longa tarefa e se torna uma interação muito lenta devido ao atraso de entrada.

Idealmente, se uma interação vier no meio da tarefa, queremos pausar nosso trabalho ocupado para que novas interações sejam tratadas imediatamente. Como podemos fazer isso?

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

Muitos setTimeouts

Primeira tentativa: fazer algo simples.

Consulte 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 sobrepostas a essas tarefas, nenhuma interação tem um atraso de entrada superior a 100 milissegundos. O navegador prioriza os listeners de eventos recebidos em relação ao trabalho de setTimeout, e as interações permanecem responsivas.

Essa estratégia funciona especialmente bem ao programar pontos de entrada separados, por exemplo, quando você precisa chamar vários recursos independentes no tempo de carregamento do aplicativo. Por padrão, apenas carregar scripts e executar tudo no momento de avaliação do script pode executar tudo em uma tarefa gigante e longa.

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

Agora com yield()

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

Exemplo:

Consulte o código completo: rendimentoy.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 pedaço de trabalho, e o navegador é capaz de responder a qualquer interação recebida, mas agora tudo o que é necessário é uma await schedulerDotYield() em vez de setTimeouts separadas, o que a torna ergonômica o suficiente para ser usada mesmo no meio de uma repetição 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 dedução, 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 a repetição blockInPiecesYieldyAborty for, fazendo o que for necessário, produzindo periodicamente a linha de execução principal para que o navegador permaneça responsivo a novas interações.

Quando um segundo clique ocorre, 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 execução novamente, ele percebe que signal.aborted agora é true e retorna imediatamente sem fazer mais trabalho.

O trabalho da linha de execução principal agora está 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 seja responsivo a novas interações. Isso permite fornecer feedback inicial rapidamente e também permite que você tome decisões, como cancelar um trabalho em andamento. Às vezes, isso significa agendar pontos de entrada como tarefas separadas. Às vezes, isso significa adicionar "rendimento" onde for conveniente.

Lembrete

  • O INP mede todas as interações.
  • Cada interação é medida da entrada até a próxima pintura, a forma como o usuário a capacidade de resposta.
  • Atrasos na entrada, duração do processamento de eventos e atrasos na apresentação afetam a capacidade de resposta da interação.
  • Você pode medir o INP e os detalhamentos das interações com o DevTools com facilidade.

Estratégias

  • Não têm códigos de longa duração (tarefas longas) nas suas páginas.
  • Remova códigos desnecessários dos listeners de eventos para depois da próxima gravação.
  • Certifique-se de que a atualização de renderização em si seja eficiente para o navegador.

Saiba mais