1. Introduzione
Questo è un codelab interattivo per imparare a misurare Interaction to Next Paint (INP) utilizzando la libreria web-vitals.
Prerequisiti
- Conoscenza dello sviluppo HTML e JavaScript.
- Consigliato: leggi la documentazione sulla metrica INP di web.dev.
Cosa imparerai a fare
- Come aggiungere la libreria
web-vitalsalla tua pagina e utilizzare i relativi dati di attribuzione. - Utilizza i dati di attribuzione per diagnosticare dove e come iniziare a migliorare l'INP.
Che cosa ti 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 funzionamento di tutte le misurazioni delle interazioni.
2. Configurazione
Recuperare ed eseguire il codice
Il codice si trova nel repository web-vitals-codelabs.
- Clona il repository nel terminale:
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git. - Vai alla directory clonata:
cd web-vitals-codelabs/measuring-inp. - Installa le dipendenze:
npm ci. - Avvia il server web:
npm run start. - Visita la pagina http://localhost:8080/ nel browser.
Prova la pagina
Questo codelab utilizza Gastropodicon (un sito di riferimento popolare sull'anatomia delle lumache) per esplorare i potenziali problemi con INP.

Prova a interagire con la pagina per capire quali interazioni sono lente.
3. Orientarsi in 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.
In questo codelab utilizzeremo sia il riquadro Prestazioni sia la console. Puoi passare da una all'altra nelle schede nella parte superiore di DevTools in qualsiasi momento.
- I problemi INP si verificano più spesso sui dispositivi mobili, quindi passa all'emulazione della visualizzazione mobile.
- Se esegui il test su un computer o un laptop, le prestazioni saranno probabilmente molto migliori rispetto a quelle di un dispositivo mobile reale. Per una visione più realistica del rendimento, fai clic sull'icona a forma di ingranaggio in alto a destra nel riquadro Rendimento, poi seleziona Rallentamento CPU 4x.

4. Installazione di web-vitals
web-vitals è una libreria JavaScript per misurare le metriche dei segnali web che i tuoi utenti riscontrano. Puoi utilizzare la libreria per acquisire questi valori e poi inviarli a un endpoint di analisi per un'analisi successiva, per capire quando e dove si verificano interazioni lente.
Esistono diversi modi per aggiungere la raccolta a una pagina. La modalità di installazione della libreria sul tuo sito dipende da come gestisci le dipendenze, dal processo di compilazione e da altri fattori. Assicurati di consultare la documentazione della libreria per tutte le opzioni disponibili.
Questo codelab verrà installato da npm e caricherà lo script direttamente per evitare di approfondire un particolare processo di compilazione.
Puoi utilizzare due versioni di web-vitals:
- La build "standard" deve essere utilizzata se vuoi monitorare i valori delle metriche dei Segnali web essenziali durante il caricamento di una pagina.
- La build "attribution" aggiunge ulteriori informazioni di debug a ogni metrica per diagnosticare il motivo per cui una metrica ha un determinato valore.
Per misurare l'INP in questo codelab, vogliamo creare l'attribuzione.
Aggiungi web-vitals al devDependencies del progetto eseguendo npm install -D web-vitals
Aggiungi web-vitals alla pagina:
Aggiungi la versione di attribuzione dello script alla fine di index.html e registra i risultati nella console:
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log);
</script>
Prova
Prova a interagire di nuovo con la pagina con la console aperta. Mentre fai clic sulla pagina, non viene registrato nulla.
L'INP viene misurato durante l'intero ciclo di vita di una pagina, pertanto, per impostazione predefinita, web-vitals non lo segnala finché l'utente non lascia o chiude la pagina. Questo è il comportamento ideale per il beaconing per qualcosa come l'analisi, ma è meno ideale per il debug interattivo.
web-vitals fornisce un'opzione reportAllChanges per report più dettagliati. Se attivata, non viene segnalata ogni interazione, ma ogni volta che un'interazione è più lenta di quelle precedenti.
Prova ad aggiungere l'opzione allo script e a interagire di nuovo con la pagina:
<script type="module">
import {onINP} from './node_modules/web-vitals/dist/web-vitals.attribution.js';
onINP(console.log, {reportAllChanges: true});
</script>
Aggiorna la pagina e le interazioni dovrebbero ora essere segnalate alla console, con aggiornamenti ogni volta che viene rilevata un'interazione più lenta. Ad esempio, prova a digitare nella casella di ricerca e poi a eliminare l'input.

5. Che cosa contiene un'attribuzione?
Iniziamo con la prima interazione che la maggior parte degli utenti avrà con la pagina, ovvero la finestra di dialogo per il consenso all'uso dei cookie.
Molte pagine hanno script che richiedono l'attivazione sincrona dei cookie quando questi vengono accettati da un utente, il che fa sì che il clic diventi un'interazione lenta. Questo è ciò che succede qui.
Fai clic su Sì per accettare i cookie (demo) e dai un'occhiata ai dati INP ora registrati nella console DevTools.

Queste informazioni di primo livello sono disponibili sia nelle build standard che in quelle di web vitals per l'attribuzione:
{
name: 'INP',
value: 344,
rating: 'needs-improvement',
entries: [...],
id: 'v4-1715732159298-8028729544485',
navigationType: 'reload',
attribution: {...},
}
Il periodo di tempo trascorso dal momento in cui l'utente ha fatto clic al successivo paint è stato di 344 millisecondi, un INP "da migliorare". L'array entries contiene tutti i valori PerformanceEntry associati a questa interazione, in questo caso un solo evento di clic.
Per scoprire cosa succede durante questo periodo, tuttavia, ci interessa di più la proprietà attribution. Per creare i dati di attribuzione, web-vitals individua quale Long Animations Frame (LoAF) si sovrappone all'evento di clic. Il LoAF può quindi fornire dati dettagliati su come è stato utilizzato il tempo durante quel frame, dagli script eseguiti al tempo trascorso in un callback, stile e layout requestAnimationFrame.
Espandi la proprietà attribution per visualizzare ulteriori informazioni. I dati sono molto più ricchi.
attribution: {
interactionTargetElement: Element,
interactionTarget: '#confirm',
interactionType: 'pointer',
inputDelay: 27,
processingDuration: 295.6,
presentationDelay: 21.4,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
Innanzitutto, ci sono informazioni su cosa è stato interagito:
interactionTargetElement: un riferimento live all'elemento con cui è stata eseguita l'interazione (se l'elemento non è stato rimosso dal DOM).interactionTarget: un selettore per trovare l'elemento all'interno della pagina.
Successivamente, la tempistica viene suddivisa in modo generale:
inputDelay: il tempo che intercorre tra l'inizio dell'interazione da parte dell'utente (ad esempio, il clic del mouse) e l'inizio dell'esecuzione del listener di eventi per quell'interazione. In questo caso, il ritardo di input è stato di soli 27 millisecondi circa, anche con la limitazione della CPU attiva.processingDuration: il tempo necessario per l'esecuzione completa dei listener di eventi. Spesso, le pagine hanno più listener per un singolo evento (ad esempiopointerdown,pointerupeclick). Se vengono eseguiti tutti nello stesso frame di animazione, verranno uniti in questo periodo di tempo. In questo caso, la durata dell'elaborazione è di 295,6 millisecondi, ovvero la maggior parte del tempo INP.presentationDelay: il tempo che intercorre dal completamento dei listener di eventi al momento in cui il browser ha terminato di disegnare il frame successivo. In questo caso, 21,4 millisecondi.
Queste fasi INP possono essere un indicatore fondamentale per diagnosticare cosa deve essere ottimizzato. La guida Ottimizzare INP fornisce ulteriori informazioni su questo argomento.
Se analizziamo più nel dettaglio, processedEventEntries contiene cinque eventi, anziché il singolo evento nell'array INP di primo livello entries. Qual è la differenza?
processedEventEntries: [
{
name: 'mouseover',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{
name: 'mousedown',
entryType: 'event',
startTime: 1801.6,
duration: 344,
processingStart: 1825.3,
processingEnd: 1825.3,
cancelable: true
},
{name: 'mousedown', ...},
{name: 'mouseup', ...},
{name: 'click', ...},
],
La voce di primo livello è l'evento INP the, in questo caso un clic. L'attribuzione processedEventEntries sono tutti gli eventi elaborati nello stesso frame. Tieni presente che include altri eventi come mouseover e mousedown, non solo l'evento di clic. Conoscere questi altri eventi può essere fondamentale se anche questi sono stati lenti, in quanto hanno contribuito a una risposta lenta.
Infine, c'è l'array longAnimationFrameEntries. Potrebbe trattarsi di una singola voce, ma ci sono casi in cui un'interazione può estendersi su più frame. Qui abbiamo il caso più semplice con un singolo frame di animazione lungo.
longAnimationFrameEntries
Espandendo la voce LoAF:
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 1823,
duration: 319,
renderStart: 2139.5,
styleAndLayoutStart: 2139.7,
firstUIEventTimestamp: 1801.6,
blockingDuration: 268,
scripts: [{...}]
}],
Qui sono presenti diversi valori utili, ad esempio la suddivisione della quantità di tempo dedicata allo stile. L'articolo sull'API Long Animation Frames descrive in modo più approfondito queste proprietà. Al momento siamo principalmente interessati alla proprietà scripts, che contiene voci che forniscono dettagli sugli script responsabili del frame a esecuzione prolungata:
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 1828.6,
executionStart: 1828.6,
duration: 294,
sourceURL: 'http://localhost:8080/third-party/cmp.js',
sourceFunctionName: '',
sourceCharPosition: 1144
}]
In questo caso, possiamo dire che il tempo è stato speso principalmente in un singolo event-listener, richiamato il giorno BUTTON#confirm.onclick. Possiamo persino vedere l'URL di origine dello script e la posizione del carattere in cui è stata definita la funzione.
Concetti chiave
Cosa si può determinare in merito a questo caso dai dati di attribuzione?
- L'interazione è stata attivata da un clic sull'elemento
button#confirm(daattribution.interactionTargete dalla proprietàinvokerin una voce di attribuzione dello script). - Il tempo è stato speso principalmente per l'esecuzione dei listener di eventi (da
attribution.processingDurationrispetto alla metrica totalevalue). - Il codice del listener di eventi lento inizia da un listener di clic definito in
third-party/cmp.js(dascripts.sourceURL).
Questi dati sono sufficienti per sapere dove dobbiamo ottimizzare.
6. Più listener di eventi
Aggiorna la pagina in modo che la console DevTools sia vuota e l'interazione per il consenso all'uso dei cookie non sia più la più lunga.
Inizia a digitare nella casella di ricerca. Che cosa mostrano i dati di attribuzione? Cosa pensi stia succedendo?
Dati di attribuzione
Innanzitutto, una scansione di alto livello di un esempio di test della demo:
{
name: 'INP',
value: 1072,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'keyboard',
inputDelay: 3.3,
processingDuration: 1060.6,
presentationDelay: 8.1,
processedEventEntries: [...],
longAnimationFrameEntries: [...],
}
}
Si tratta di un valore INP scarso (con la limitazione della CPU abilitata) derivante da un'interazione da tastiera con l'elemento input#search-terms. La maggior parte del tempo, 1061 millisecondi su un totale di 1072 millisecondi di INP, è stata dedicata alla durata dell'elaborazione.
Le voci scripts sono più interessanti, però.
Layout thrashing
La prima voce dell'array scripts ci fornisce un contesto prezioso:
scripts: [{
name: 'script',
invoker: 'BUTTON#confirm.onclick',
invokerType: 'event-listener',
startTime: 4875.6,
executionStart: 4875.6,
duration: 497,
forcedStyleAndLayoutDuration: 388,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'handleSearch',
sourceCharPosition: 940
},
...]
La maggior parte della durata dell'elaborazione si verifica durante l'esecuzione di questo script, che è un listener input (il chiamante è INPUT#search-terms.oninput). Vengono forniti il nome della funzione (handleSearch) e la posizione del carattere all'interno del file di origine index.js.
Tuttavia, è presente una nuova proprietà: forcedStyleAndLayoutDuration. Questo è il tempo trascorso all'interno di questa chiamata di script in cui il browser è stato costretto a riorganizzare il layout della pagina. In altre parole, il 78% del tempo (388 millisecondi su 497) dedicato all'esecuzione di questo listener di eventi è stato effettivamente speso per il layout thrashing.
La risoluzione di questo problema dovrebbe essere una priorità assoluta.
Ascoltatori ricorrenti
Singolarmente, non c'è nulla di particolarmente degno di nota nelle due voci di script successive:
scripts: [...,
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5375.3,
executionStart: 5375.3,
duration: 124,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526,
},
{
name: 'script',
invoker: '#document.onkeyup',
invokerType: 'event-listener',
startTime: 5673.9,
executionStart: 5673.9,
duration: 95,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: '',
sourceCharPosition: 1526
}]
Entrambe le voci sono listener keyup, che vengono eseguite una dopo l'altra. I listener sono funzioni anonime (quindi non viene segnalato nulla nella proprietà sourceFunctionName), ma abbiamo comunque un file sorgente e una posizione del carattere, quindi possiamo trovare la posizione del codice.
La cosa strana è che entrambi provengono dallo stesso file di origine e dalla stessa posizione del carattere.
Il browser ha finito per elaborare più pressioni di tasti in un singolo frame di animazione, il che ha portato questo listener di eventi a essere eseguito due volte prima che potesse essere visualizzato qualcosa.
Questo effetto può anche essere cumulativo: più tempo impiegano i listener di eventi a completarsi, più eventi di input aggiuntivi possono verificarsi, prolungando ulteriormente l'interazione lenta.
Poiché si tratta di un'interazione di ricerca/completamento automatico, il debouncing dell'input sarebbe una buona strategia per elaborare al massimo una pressione dei tasti per frame.
7. Ritardo input
Il motivo tipico dei ritardi di input, ovvero il tempo che intercorre tra l'interazione dell'utente e il momento in cui un listener di eventi può iniziare a elaborare l'interazione, è che il thread principale è occupato. Ciò potrebbe essere dovuto a diversi motivi:
- La pagina è in fase di caricamento e il thread principale è occupato a svolgere il lavoro iniziale di configurazione del DOM, layout e stile della pagina, nonché valutazione ed esecuzione degli script.
- La pagina è generalmente occupata, ad esempio con calcoli, animazioni basate su script o annunci.
- Le interazioni precedenti richiedono così tanto tempo per essere elaborate che ritardano le interazioni future, come si è visto nell'ultimo esempio.
La pagina demo ha una funzionalità segreta: se fai clic sul logo della lumaca nella parte superiore della pagina, l'animazione inizia e viene eseguito un lavoro JavaScript pesante sul thread principale.
- Fai clic sul logo della lumaca per avviare l'animazione.
- Le attività JavaScript vengono attivate quando la lumaca si trova nella parte inferiore del rimbalzo. Prova a interagire con la pagina il più vicino possibile alla parte inferiore del rimbalzo e vedi quanto INP riesci ad attivare.
Ad esempio, anche se non attivi altri listener di eventi, ad esempio facendo clic e mettendo a fuoco la casella di ricerca proprio mentre la lumaca rimbalza, il lavoro del thread principale farà sì che la pagina non risponda per un periodo di tempo notevole.
In molte pagine, il lavoro pesante del thread principale non si comporta in questo modo, ma questa è una buona dimostrazione per vedere come può essere identificabile nei dati di attribuzione INP.
Ecco un esempio di attribuzione che si concentra solo sulla casella di ricerca durante il rimbalzo lento:
{
name: 'INP',
value: 728,
rating: 'poor',
attribution: {
interactionTargetElement: Element,
interactionTarget: '#search-terms',
interactionType: 'pointer',
inputDelay: 702.3,
processingDuration: 4.9,
presentationDelay: 20.8,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 2064.8,
duration: 790,
renderStart: 2065,
styleAndLayoutStart: 2854.2,
firstUIEventTimestamp: 0,
blockingDuration: 740,
scripts: [{...}]
}]
}
}
Come previsto, i listener di eventi sono stati eseguiti rapidamente, con una durata di elaborazione di 4,9 millisecondi, e la maggior parte dell'interazione scadente è stata spesa nel ritardo di input, che ha richiesto 702,3 millisecondi su un totale di 728.
Questa situazione può essere difficile da eseguire il debug. Anche se sappiamo con cosa e come ha interagito l'utente, sappiamo anche che questa parte dell'interazione è stata completata rapidamente e non ha rappresentato un problema. Invece, è stato qualcos'altro nella pagina a ritardare l'inizio dell'elaborazione dell'interazione, ma come facciamo a sapere da dove iniziare a cercare?
Le voci dello script LoAF sono qui per salvarti la giornata:
scripts: [{
name: 'script',
invoker: 'SPAN.onanimationiteration',
invokerType: 'event-listener',
startTime: 2065,
executionStart: 2065,
duration: 788,
sourceURL: 'http://localhost:8080/js/index.js',
sourceFunctionName: 'cryptodaphneCoinHandler',
sourceCharPosition: 1831
}]
Anche se questa funzione non aveva nulla a che fare con l'interazione, ha rallentato il frame dell'animazione, quindi è inclusa nei dati LoAF uniti all'evento di interazione.
Da qui possiamo vedere come è stata attivata la funzione che ha ritardato l'elaborazione dell'interazione (da un listener animationiteration), esattamente quale funzione era responsabile e dove si trovava nei nostri file sorgente.
8. Ritardo nella presentazione: quando un aggiornamento non viene visualizzato
Il ritardo di presentazione misura il tempo che intercorre tra il termine dell'esecuzione dei listener di eventi e il momento in cui il browser è in grado di disegnare un nuovo frame sullo schermo, mostrando all'utente un feedback visibile.
Aggiorna la pagina per reimpostare di nuovo il valore INP, quindi apri il menu di navigazione. C'è un problema quando si apre.
Ecco un esempio
{
name: 'INP',
value: 376,
rating: 'needs-improvement',
delta: 352,
attribution: {
interactionTarget: '#sidenav-button>svg',
interactionType: 'pointer',
inputDelay: 12.8,
processingDuration: 14.7,
presentationDelay: 348.5,
longAnimationFrameEntries: [{
name: 'long-animation-frame',
startTime: 651,
duration: 365,
renderStart: 673.2,
styleAndLayoutStart: 1004.3,
firstUIEventTimestamp: 138.6,
blockingDuration: 315,
scripts: [{...}]
}]
}
}
Questa volta è il ritardo di presentazione a costituire la maggior parte dell'interazione lenta. Ciò significa che qualsiasi elemento che blocca il thread principale si verifica dopo il completamento dei listener di eventi.
scripts: [{
entryType: 'script',
invoker: 'FrameRequestCallback',
invokerType: 'user-callback',
startTime: 673.8,
executionStart: 673.8,
duration: 330,
sourceURL: 'http://localhost:8080/js/side-nav.js',
sourceFunctionName: '',
sourceCharPosition: 1193,
}]
Esaminando la singola voce nell'array scripts, vediamo che il tempo viene trascorso in un user-callback da un FrameRequestCallback. Questa volta il ritardo della presentazione è causato da un callback requestAnimationFrame.
9. Conclusione
Aggregazione dei dati dei campi
È importante riconoscere che tutto questo è più semplice quando si esamina una singola voce di attribuzione INP da un singolo caricamento di pagina. Come possono essere aggregati questi dati per eseguire il debug dell'INP in base ai dati di campo? La quantità di dettagli utili rende il compito più difficile.
Ad esempio, è estremamente utile sapere quale elemento della pagina è una fonte comune di interazioni lente. Tuttavia, se la tua pagina ha nomi di classi CSS compilati che cambiano da una build all'altra, i selettori web-vitals dello stesso elemento potrebbero essere diversi nelle varie build.
Devi invece pensare alla tua particolare applicazione per determinare cosa è più utile e come possono essere aggregati i dati. Ad esempio, prima di inviare i dati di attribuzione tramite beacon, puoi sostituire il selettore web-vitals con un identificatore personalizzato, in base al componente in cui si trova il target o ai ruoli ARIA che il target soddisfa.
Allo stesso modo, le voci scripts potrebbero avere hash basati su file nei relativi percorsi sourceURL che rendono difficile la combinazione, ma potresti rimuovere gli hash in base al processo di build noto prima di inviare nuovamente i dati.
Purtroppo, non esiste un percorso semplice con dati così complessi, ma anche l'utilizzo di un sottoinsieme è più utile di nessun dato di attribuzione per il processo di debug.
Attribuzione ovunque.
L'attribuzione INP basata su LoAF è un potente strumento di debug. Offre dati granulari su cosa è successo esattamente durante un INP. In molti casi, può indicarti la posizione esatta di uno script in cui devi iniziare i tuoi interventi di ottimizzazione.
Ora puoi utilizzare i dati di attribuzione INP su qualsiasi sito.
Anche se non hai accesso alla modifica di una pagina, puoi ricreare la procedura di questo codelab eseguendo il seguente snippet nella console di DevTools per vedere cosa riesci a trovare:
const script = document.createElement('script');
script.src = 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.iife.js';
script.onload = function () {
webVitals.onINP(console.log, {reportAllChanges: true});
};
document.head.appendChild(script);