Informazioni sull'interazione con Next Paint (INP)

Informazioni su Interaction to Next Paint (INP)

Informazioni su questo codelab

subjectUltimo aggiornamento: gen 9, 2025
account_circleScritto da: Michal Mocny, Brendan Kenny

1. Introduzione

Una demo interattiva e un codelab per scoprire di più su Interaction to Next Paint (INP).

Un diagramma che mostra un'interazione sul thread principale. L'utente esegue un input durante l'esecuzione delle attività di blocco. L'input viene ritardato fino al completamento di queste attività, dopodiché vengono eseguiti i listener di eventi pointerup, mouseup e click, quindi viene avviato il rendering e la pittura fino alla presentazione del frame successivo

Prerequisiti

Cosa imparerai

  • In che modo l'interazione tra le interazioni degli utenti e la gestione di queste interazioni influisce sull'adattabilità della pagina.
  • Come ridurre ed eliminare i ritardi per un'esperienza utente fluida.

Cosa serve

  • Un computer in grado di clonare il codice da GitHub ed eseguire comandi npm.
  • Un editor di testo.
  • Una versione recente di Chrome per il corretto funzionamento di tutte le misurazioni delle interazioni.

2. Configurazione

Recuperare ed eseguire il codice

Il codice si trova nel repository web-vitals-codelabs.

  1. Clona il repository nel terminale: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. Vai alla directory clonata: cd web-vitals-codelabs/understanding-inp
  3. Installa le dipendenze: npm ci
  4. Avvia il server web: npm run start
  5. Visita la pagina http://localhost:5173/understanding-inp/ nel browser.

Panoramica dell'app

Nella parte superiore della pagina si trovano un contatore Punteggio e un pulsante Incremento. Una dimostrazione classica di reattività e risposta.

Screenshot dell'app demo per questo codelab

Sotto il pulsante sono presenti quattro misurazioni:

  • INP: il punteggio INP attuale, che in genere corrisponde all'interazione peggiore.
  • Interazione: il punteggio dell'interazione più recente.
  • FPS: i frame al secondo del thread principale della pagina.
  • Timer: un'animazione del timer in esecuzione per visualizzare il jank.

Le voci FPS e Timer non sono necessarie per misurare le interazioni. Vengono aggiunti solo per semplificare la visualizzazione della reattività.

Prova

Prova a interagire con il pulsante Aumenta e guarda il punteggio aumentare. I valori di INP e Interazione cambiano a ogni incremento?

INP misura il tempo che intercorre dal momento in cui l'utente interagisce fino a quando la pagina mostra effettivamente l'aggiornamento sottoposto a rendering all'utente.

3. Misurare le interazioni con Chrome DevTools

Apri DevTools dal menu Altri strumenti > Strumenti per sviluppatori, facendo clic con il tasto destro del mouse sulla pagina e selezionando Ispeziona o utilizzando una scorciatoia da tastiera.

Passa al riquadro Rendimento, che utilizzerai per misurare le interazioni.

Uno screenshot del pannello Rendimento di DevTools accanto all'app

Successivamente, acquisisci un'interazione nel riquadro Prestazioni.

  1. Premi il pulsante per registrare.
  2. Interagisci con la pagina (premi il pulsante Incrementa).
  3. Interrompi la registrazione.

Nella sequenza temporale risultante, troverai una traccia Interazioni. Espandilo facendo clic sul triangolo a sinistra.

Una dimostrazione animata della registrazione di un'interazione utilizzando il pannello Rendimento di DevTools

Vengono visualizzate due interazioni. Aumenta lo zoom sulla seconda immagine scorrendo o tenendo premuto il tasto W.

Uno screenshot del riquadro Prestazioni di DevTools, con il cursore che passa il mouse sopra l'interazione nel riquadro e una descrizione comando che elenca la breve durata dell'interazione

Se passi il mouse sopra l'interazione, puoi vedere che è stata veloce, senza tempi di durata dell'elaborazione e con un tempo minimo di ritardo di input e ritardo di presentazione, la cui durata esatta dipende dalla velocità della macchina.

4. Listener di eventi di lunga durata

Apri il file index.js e rimuovi il commento dalla funzione blockFor all'interno del listener di eventi.

Vedi il codice completo: click_block.html

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

Salva il file. Il server vedrà la modifica e aggiornerà la pagina.

Prova a interagire di nuovo con la pagina. Le interazioni ora saranno notevolmente più lente.

Traccia del rendimento

Esegui un'altra registrazione nel riquadro Prestazioni per vedere come appare.

Un'interazione di un secondo nel riquadro Prestazioni

Quella che una volta era un'interazione breve ora richiede un secondo intero.

Quando passi il mouse sopra l'interazione, noterai che il tempo è quasi interamente dedicato alla "Durata dell'elaborazione", ovvero il tempo necessario per eseguire i callback del listener di eventi. Poiché la chiamata di blocco blockFor si trova interamente all'interno del listener di eventi, è qui che viene impiegato il tempo.

5. Experiment: processing duration

Prova a riorganizzare il lavoro del listener di eventi per vedere l'effetto sull'INP.

Aggiorna prima l'interfaccia utente

Cosa succede se inverti l'ordine delle chiamate JS: aggiorni prima la UI, poi blocchi?

Visualizza il codice completo: ui_first.html

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

Hai notato che l'interfaccia utente è apparsa prima? L'ordine influisce sui punteggi INP?

Prova a eseguire una traccia ed esamina l'interazione per verificare se sono presenti differenze.

Listener separati

Cosa succede se sposti il lavoro in un listener di eventi separato? Aggiorna la UI in un listener di eventi e blocca la pagina da un listener separato.

Visualizza il codice completo: two_click.html

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

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

Che aspetto ha ora nel riquadro del rendimento?

Diversi tipi di eventi

La maggior parte delle interazioni attiverà molti tipi di eventi, dagli eventi del puntatore o della tastiera a eventi di passaggio del mouse, messa a fuoco/sfocatura ed eventi sintetici come beforechange e beforeinput.

Molte pagine reali hanno listener per molti eventi diversi.

Cosa succede se modifichi i tipi di eventi per i listener di eventi? Ad esempio, sostituire uno dei listener di eventi click con pointerup o mouseup?

Visualizza il codice completo: diff_handlers.html

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

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

Nessun aggiornamento dell'interfaccia utente

Cosa succede se rimuovi la chiamata per aggiornare l'interfaccia utente dal listener di eventi?

Visualizza il codice completo: no_ui.html

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

6. Risultati dell'esperimento sulla durata dell'elaborazione

Traccia delle prestazioni: aggiorna prima l'interfaccia utente

Visualizza il codice completo: ui_first.html

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

Se esamini la registrazione di un pannello Rendimento in cui viene fatto clic sul pulsante, puoi notare che i risultati non sono cambiati. Sebbene un aggiornamento dell'interfaccia utente sia stato attivato prima del codice di blocco, il browser non ha aggiornato ciò che è stato visualizzato sullo schermo fino al completamento del listener di eventi, il che significa che l'interazione ha comunque richiesto poco più di un secondo per essere completata.

Un'interazione di un secondo nel riquadro Prestazioni

Traccia di rendimento: ascoltatori separati

Visualizza il codice completo: two_click.html

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

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

Anche in questo caso, non c'è alcuna differenza funzionale. L'interazione richiede comunque un secondo intero.

Se ingrandisci l'interazione con il clic, vedrai che vengono chiamate due funzioni diverse in seguito all'evento click.

Come previsto, il primo, l'aggiornamento della UI, viene eseguito molto rapidamente, mentre il secondo richiede un secondo intero. Tuttavia, la somma dei loro effetti si traduce nella stessa interazione lenta per l'utente finale.

Uno sguardo ravvicinato all'interazione di un secondo in questo esempio, che mostra la prima chiamata di funzione che richiede meno di un millisecondo per essere completata

Traccia del rendimento: diversi tipi di eventi

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

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

Questi risultati sono molto simili. L'interazione dura ancora un secondo intero; l'unica differenza è che il listener click più breve per l'aggiornamento della UI viene eseguito dopo il listener di blocco pointerup.

Un'immagine ingrandita dell'interazione di un secondo in questo esempio, che mostra il listener di eventi di clic che impiega meno di un millisecondo per essere completato, dopo il listener pointerup

Traccia delle prestazioni: nessun aggiornamento della UI

Visualizza il codice completo: no_ui.html

button.addEventListener('click', () => {
  blockFor
(1000);
 
// score.incrementAndUpdateUI();
});
  • Il punteggio non viene aggiornato, ma la pagina sì.
  • Animazioni, effetti CSS, azioni predefinite dei componenti web (input del modulo), inserimento di testo ed evidenziazione del testo continuano a essere aggiornati.

In questo caso, il pulsante passa a uno stato attivo e torna indietro quando viene selezionato, il che richiede un rendering da parte del browser, il che significa che è ancora presente un INP.

Poiché il listener di eventi ha bloccato il thread principale per un secondo impedendo il rendering della pagina, l'interazione richiede comunque un secondo intero.

L'acquisizione di una registrazione del pannello Rendimento mostra l'interazione praticamente identica a quelle precedenti.

Un'interazione di un secondo nel riquadro Prestazioni

Concetti chiave

Qualsiasi codice in esecuzione in qualsiasi listener di eventi ritarderà l'interazione.

  • Sono inclusi i listener registrati da script e framework diversi o il codice della libreria eseguito nei listener, ad esempio un aggiornamento dello stato che attiva il rendering di un componente.
  • Non solo il tuo codice, ma anche tutti gli script di terze parti.

È un problema comune.

Infine, il fatto che il tuo codice non attivi un rendering non significa che un rendering non attenderà il completamento dei listener di eventi lenti.

7. Experiment: input delay

Che dire del codice a esecuzione prolungata al di fuori dei listener di eventi? Ad esempio:

  • Se avevi un <script> a caricamento ritardato che bloccava la pagina in modo casuale durante il caricamento.
  • Una chiamata API, ad esempio setInterval, che blocca periodicamente la pagina?

Prova a rimuovere blockFor dal listener di eventi e ad aggiungerlo a un setInterval():

Visualizza il codice completo: input_delay.html

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


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

Che cosa succede?

8. Risultati dell&#39;esperimento sul ritardo input

Visualizza il codice completo: input_delay.html

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


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

La registrazione di un clic sul pulsante che si verifica durante l'esecuzione dell'attività di blocco setInterval comporta un'interazione di lunga durata, anche se non viene eseguito alcun lavoro di blocco nell'interazione stessa.

Questi periodi di lunga durata vengono spesso chiamati attività di lunga durata.

Se passi il mouse sopra l'interazione in DevTools, vedrai che ora il tempo di interazione è attribuito principalmente al ritardo di input, non alla durata dell'elaborazione.

Il pannello Prestazioni di DevTools che mostra un task di blocco di un secondo, un&#39;interazione che si verifica a metà del task e un&#39;interazione di 642 millisecondi, attribuita principalmente al ritardo di input

Tieni presente che non sempre influisce sulle interazioni. Se non fai clic durante l'esecuzione dell'attività, potresti avere fortuna. Questi starnuti "casuali" possono essere un incubo da risolvere quando causano problemi solo a volte.

Un modo per individuarli è misurare le attività lunghe (o Long Animation Frames) e il Total Blocking Time.

9. Presentazione lenta

Finora abbiamo esaminato le prestazioni di JavaScript tramite il ritardo di input o i listener di eventi, ma cos'altro influisce sul rendering del successivo paint?

Beh, aggiornando la pagina con effetti costosi.

Anche se l'aggiornamento della pagina avviene rapidamente, il browser potrebbe comunque dover lavorare sodo per eseguirne il rendering.

Nel thread principale:

  • Framework UI che devono eseguire il rendering degli aggiornamenti dopo le modifiche dello stato
  • Le modifiche al DOM o l'attivazione/disattivazione di molti selettori di query CSS costosi possono attivare molti stili, layout e paint.

Fuori dal thread principale:

  • Utilizzo di CSS per potenziare gli effetti della GPU
  • Aggiunta di immagini ad alta risoluzione molto grandi
  • Utilizzo di SVG/Canvas per disegnare scene complesse

Schizzo dei diversi elementi del rendering sul web

RenderingNG

Alcuni esempi comunemente trovati sul web:

  • Un sito SPA che ricostruisce l'intero DOM dopo aver fatto clic su un link, senza mettere in pausa per fornire un feedback visivo iniziale.
  • Una pagina di ricerca che offre filtri di ricerca complessi con un'interfaccia utente dinamica, ma esegue listener costosi per farlo.
  • Un pulsante di attivazione/disattivazione della modalità Buio che attiva lo stile/il layout per l'intera pagina

10. Experiment: presentation delay

Dispositivo requestAnimationFrame lento

Simuliamo un lungo ritardo della presentazione utilizzando l'API requestAnimationFrame().

Sposta la chiamata blockFor in un callback requestAnimationFrame in modo che venga eseguita dopo la restituzione del listener di eventi:

Visualizza il codice completo: presentation_delay.html

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

Che cosa succede?

11. Risultati dell&#39;esperimento sul ritardo presentazione

Visualizza il codice completo: presentation_delay.html

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

L'interazione dura un secondo, quindi cosa è successo?

requestAnimationFrame richiede un richiamo prima della prossima verniciatura. Poiché l'INP misura il tempo che intercorre tra l'interazione e il rendering successivo, il blockFor(1000) in requestAnimationFrame continua a bloccare il rendering successivo per un secondo intero.

Un&#39;interazione di un secondo nel riquadro Prestazioni

Tuttavia, nota due cose:

  • Al passaggio del mouse, vedrai che tutto il tempo di interazione viene ora speso in "ritardo di presentazione", poiché il blocco del thread principale si verifica dopo la restituzione del listener di eventi.
  • La radice dell'attività del thread principale non è più l'evento di clic, ma "Animation Frame Fired".

12. Diagnostica delle interazioni

In questa pagina di test, la reattività è molto visiva, con i punteggi, i timer e l'interfaccia utente del contatore, ma quando si testa la pagina media è più sottile.

Quando le interazioni durano a lungo, non è sempre chiaro quale sia la causa. Potrebbe trattarsi di:

  • Ritardo input?
  • Durata dell'elaborazione dell'evento.
  • Ritardo presentazione?

In qualsiasi pagina, puoi utilizzare DevTools per misurare la reattività. Per prendere l'abitudine, prova questo flusso:

  1. Naviga sul web come faresti normalmente.
  2. Tieni d'occhio il log delle interazioni nella visualizzazione delle metriche in tempo reale del riquadro Prestazioni di DevTools.
  3. Se noti un'interazione con prestazioni scarse, prova a ripeterla:
  • Se non riesci a riprodurlo, utilizza il log delle interazioni per ottenere informazioni.
  • Se riesci a riprodurlo, registra una traccia nel pannello Rendimento.

Tutti i ritardi

Prova ad aggiungere un po' di tutti questi problemi alla pagina:

Visualizza il codice completo: all_the_things.html

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

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

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

Poi utilizza la console e il pannello del rendimento per diagnosticare i problemi.

13. Esperimento: lavoro asincrono

Poiché puoi avviare effetti non visivi all'interno delle interazioni, ad esempio effettuare richieste di rete, avviare timer o semplicemente aggiornare lo stato globale, cosa succede quando questi effetti alla fine aggiornano la pagina?

Finché è consentito il rendering del next paint dopo un'interazione, anche se il browser decide che non è necessario un nuovo aggiornamento del rendering, la misurazione dell'interazione si interrompe.

Per provare questa soluzione, continua ad aggiornare la UI dal listener di clic, ma esegui il lavoro di blocco dal timeout.

Visualizza il codice completo: timeout_100.html

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

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

Che cosa succede ora?

14. Risultati dell&#39;esperimento sul lavoro asincrono

Visualizza il codice completo: timeout_100.html

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

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

Un&#39;interazione di 27 millisecondi con un&#39;attività di un secondo che ora si verifica più avanti nella traccia

L'interazione ora è breve perché il thread principale è disponibile immediatamente dopo l'aggiornamento della UI. L'attività di blocco prolungato continua a essere eseguita, ma viene eseguita qualche tempo dopo il rendering, in modo che l'utente riceva un feedback immediato dell'interfaccia utente.

Lezione: se non riesci a rimuoverlo, almeno spostalo.

Metodi

Possiamo fare meglio di un setTimeout fisso di 100 millisecondi? Probabilmente vogliamo ancora che il codice venga eseguito il più rapidamente possibile, altrimenti lo avremmo rimosso.

Obiettivo:

  • L'interazione verrà eseguita incrementAndUpdateUI().
  • blockFor() verrà eseguito il prima possibile, ma non bloccherà la pittura successiva.
  • Ciò si traduce in un comportamento prevedibile senza "timeout magici".

Alcuni modi per farlo includono:

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

"requestPostAnimationFrame"

A differenza di requestAnimationFrame da solo (che tenterà di essere eseguito prima del rendering successivo e di solito comporterà comunque un'interazione lenta), requestAnimationFrame + setTimeout costituisce un semplice polyfill per requestPostAnimationFrame, eseguendo il callback dopo il rendering successivo.

Visualizza il codice completo: raf+task.html

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

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

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

Per l'ergonomia, puoi persino racchiuderlo in una promessa:

Visualizza il codice completo: raf+task2.html

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

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

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

15. Interazioni multiple (e clic furiosi)

Spostare le attività di blocco lunghe può essere utile, ma queste attività continuano a bloccare la pagina, influendo sulle interazioni future, nonché su molte altre animazioni e aggiornamenti della pagina.

Prova di nuovo la versione della pagina con blocco asincrono (o la tua se hai creato una variante del rinvio del lavoro nell'ultimo passaggio):

Visualizza il codice completo: timeout_100.html

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

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

Cosa succede se fai clic più volte rapidamente?

Traccia del rendimento

Per ogni clic, viene accodata un'attività della durata di un secondo, garantendo che il thread principale venga bloccato per un periodo di tempo considerevole.

Più attività di un secondo nel thread principale, che causano interazioni lente fino a 800 ms

Quando queste attività lunghe si sovrappongono ai nuovi clic in arrivo, si verificano interazioni lente anche se il listener di eventi viene restituito quasi immediatamente. Abbiamo creato la stessa situazione dell'esperimento precedente con i ritardi di input. Solo che questa volta il ritardo di input non proviene da un setInterval, ma dal lavoro attivato da listener di eventi precedenti.

Strategie

Idealmente, vogliamo rimuovere completamente le attività lunghe.

  • Rimuovi completamente il codice non necessario, in particolare gli script.
  • Ottimizza il codice per evitare l'esecuzione di attività lunghe.
  • Interrompi il lavoro obsoleto quando arrivano nuove interazioni.

16. Strategia 1: debounce

Una strategia classica. Quando le interazioni arrivano in rapida successione e l'elaborazione o gli effetti di rete sono costosi, ritarda l'inizio del lavoro di proposito in modo da poterlo annullare e riavviare. Questo pattern è utile per interfacce utente come i campi di completamento automatico.

  • Utilizza setTimeout per ritardare l'avvio di operazioni costose, con un timer, ad esempio da 500 a 1000 millisecondi.
  • Salva l'ID timer quando lo fai.
  • Se arriva una nuova interazione, annulla il timer precedente utilizzando clearTimeout.

Vedi il codice completo: debounce.html

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

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

Traccia del rendimento

Più interazioni, ma un solo compito lungo di lavoro come risultato di tutte

Nonostante i numerosi clic, viene eseguita una sola attività blockFor, che attende un secondo intero senza clic prima di essere eseguita. Per le interazioni che si verificano a raffica, come la digitazione in un input di testo o i target degli elementi che dovrebbero ricevere più clic rapidi, questa è la strategia ideale da utilizzare per impostazione predefinita.

17. Strategia 2: interrompi il lavoro a lunga esecuzione

Esiste ancora la sfortunata possibilità che un ulteriore clic arrivi subito dopo il periodo di rimbalzo, si verifichi nel bel mezzo di questa lunga attività e diventi un'interazione molto lenta a causa del ritardo di input.

Idealmente, se un'interazione si verifica nel bel mezzo di un'attività, vogliamo interrompere il nostro lavoro in modo che le nuove interazioni vengano gestite immediatamente. Come possiamo farlo?

Esistono alcune API come isInputPending, ma in genere è meglio dividere le attività lunghe in blocchi.

Molti setTimeout

Primo tentativo: fai qualcosa di semplice.

Visualizza il codice 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);
 
});
});

In questo modo, il browser può pianificare ogni attività singolarmente e l'input può avere una priorità più elevata.

Più interazioni, ma tutto il lavoro pianificato è stato suddiviso in molte attività più piccole

Siamo tornati a cinque secondi di lavoro per cinque clic, ma ogni attività di un secondo per clic è stata suddivisa in dieci attività da 100 millisecondi. Di conseguenza, anche con più interazioni che si sovrappongono a queste attività, nessuna interazione ha un ritardo di input superiore a 100 millisecondi. Il browser dà la priorità ai listener di eventi in entrata rispetto al lavoro setTimeout e le interazioni rimangono reattive.

Questa strategia funziona particolarmente bene quando pianifichi punti di ingresso separati, ad esempio se hai una serie di funzionalità indipendenti che devi chiamare al momento del caricamento dell'applicazione. Il semplice caricamento degli script e l'esecuzione di tutto al momento della valutazione dello script potrebbero eseguire tutto in un'unica attività lunga per impostazione predefinita.

Tuttavia, questa strategia non funziona altrettanto bene per separare il codice strettamente accoppiato, come un ciclo for che utilizza uno stato condiviso.

Ora con yield()

Tuttavia, possiamo sfruttare async e await moderni per aggiungere facilmente "punti di rendimento" a qualsiasi funzione JavaScript.

Ad esempio:

Visualizza il codice 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);
});

Come prima, il thread principale viene interrotto dopo un blocco di lavoro e il browser è in grado di rispondere a qualsiasi interazione in entrata, ma ora è necessario solo un await schedulerDotYield() anziché setTimeout separati, il che lo rende abbastanza ergonomico da usare anche nel mezzo di un ciclo for.

Ora con AbortContoller()

Questo metodo ha funzionato, ma ogni interazione pianifica più lavoro, anche se sono arrivate nuove interazioni che potrebbero aver modificato il lavoro da svolgere.

Con la strategia di eliminazione dei rimbalzi, abbiamo annullato il timeout precedente a ogni nuova interazione. Possiamo fare qualcosa di simile qui? Un modo per farlo è utilizzare un AbortController():

Visualizza il codice 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 arriva un clic, inizia il ciclo blockInPiecesYieldyAborty for, che esegue il lavoro necessario cedendo periodicamente il thread principale in modo che il browser rimanga reattivo alle nuove interazioni.

Quando arriva un secondo clic, il primo ciclo viene contrassegnato come annullato con AbortController e viene avviato un nuovo ciclo blockInPiecesYieldyAborty. La volta successiva che il primo ciclo è pianificato per essere eseguito di nuovo, nota che signal.aborted ora è true e viene restituito immediatamente senza ulteriori operazioni.

Il lavoro del thread principale è ora suddiviso in molti piccoli pezzi, le interazioni sono brevi e il lavoro dura solo il tempo necessario

18. Conclusione

La suddivisione di tutte le attività lunghe consente a un sito di rispondere a nuove interazioni. In questo modo puoi fornire rapidamente un feedback iniziale e prendere decisioni come interrompere il lavoro in corso. A volte, ciò significa pianificare i punti di ingresso come attività separate. A volte significa aggiungere punti di "rendimento" dove è più conveniente.

Va ricordato che

  • L'INP misura tutte le interazioni.
  • Ogni interazione viene misurata dall'input al successivo rendering, il modo in cui l'utente vede la reattività.
  • Il ritardo input, la durata dell'elaborazione degli eventi e il ritardo di presentazione influenzano tutti la reattività dell'interazione.
  • Con DevTools puoi misurare facilmente l'INP e le suddivisioni delle interazioni.

Strategie

  • Non avere codice a esecuzione prolungata (attività lunghe) nelle tue pagine.
  • Sposta il codice non necessario dai listener di eventi fino al rendering successivo.
  • Assicurati che l'aggiornamento del rendering sia efficiente per il browser.

Scopri di più