Informazioni sull'interazione con Next Paint (INP)

1. Introduzione

Una demo interattiva e un codelab per informazioni su Interazione con Next Paint (INP).

Un diagramma che mostra un'interazione sul thread principale. L'utente inserisce un input mentre blocca l'esecuzione delle attività. L'input viene ritardato fino al completamento di queste attività, dopodiché vengono eseguiti i listener di eventi puntatoreup, mouseup e clic. Successivamente, il rendering e il disegno vengono avviati fino alla visualizzazione del frame successivo

Prerequisiti

  • Conoscenza dello sviluppo HTML e JavaScript.
  • Azione consigliata: leggi la documentazione INP.

Cosa imparerai

  • In che modo l'interazione delle interazioni degli utenti e la gestione di queste interazioni influiscono sulla reattività della pagina.
  • Come ridurre ed eliminare i ritardi per un'esperienza utente senza problemi.

Cosa serve

  • Un computer con la possibilità 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

Ottieni ed esegui 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. Passa nella directory clonata: cd web-vitals-codelabs/understanding-inp
  3. Installa dipendenze: npm ci
  4. Avvia il server web: npm run start
  5. Visita la pagina http://localhost:5173/understanding-inp/ nel tuo browser.

Panoramica dell'app

Nella parte superiore della pagina sono presenti un contatore Punteggio e il pulsante Aumenta. Una demo classica di reattività e reattività!

Uno screenshot dell'app demo per questo codelab

Sotto il pulsante ci sono 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 con timer in esecuzione per aiutare a visualizzare i jank.

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

Prova

Prova a interagire con il pulsante Aumenta e guarda l'aumento del punteggio. I valori INP e Interazione cambiano a ogni incremento?

L'INP misura il tempo che intercorre tra il momento in cui l'utente interagisce e il momento in cui la pagina mostra effettivamente all'utente l'aggiornamento che ha eseguito il rendering.

3. Misurazione delle interazioni con Chrome DevTools

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

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

Uno screenshot del riquadro Prestazioni di DevTools insieme all'app.

Quindi, acquisisci un'interazione nel riquadro Rendimento.

  1. Premi il pulsante per registrare.
  2. Interagisci con la pagina (premi il pulsante Aumenta).
  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 riquadro delle prestazioni di DevTools

Vengono visualizzate due interazioni. Aumenta lo zoom del secondo video scorrendo o tenendo premuto il tasto W.

Uno screenshot del riquadro Prestazioni di DevTools, il cursore che passa sopra l'interazione nel riquadro e una descrizione comando che elenca le brevi tempistiche dell'interazione

Passando il mouse sopra l'interazione, puoi vedere che l'interazione è stata veloce, non ha impiegato tempo per la durata dell'elaborazione e la quantità di tempo minima in ritardo dell'input e ritardo nella presentazione, la cui lunghezza esatta dipenderà dalla velocità della macchina.

4. Listener di eventi a lunga durata

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

Visualizza 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 saranno ora notevolmente più lente.

Traccia prestazioni

Registra un'altra registrazione nel riquadro Rendimento per vedere come appare.

Un'interazione di un secondo nel riquadro Rendimento.

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

Quando passi il mouse sopra l'interazione, puoi notare che il tempo è quasi interamente speso in "Durata di elaborazione", ovvero il tempo impiegato per eseguire i callback del listener di eventi. Dal momento che la chiamata blockFor che blocca è interamente all'interno del listener di eventi, il tempo passa a questo punto.

5. Esperimento: durata di elaborazione

Prova dei modi per riorganizzare il lavoro dell'ascoltatore di eventi per vedere l'effetto su INP.

Prima aggiorna l'UI

Cosa succede se inverti l'ordine delle chiamate js: aggiorni prima l'UI e poi blocchi?

Visualizza il codice completo: ui_first.html

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

Hai notato che la UI è stata visualizzata prima? L'ordine influisce sui punteggi INP?

Prova a tracciare ed esaminare l'interazione per vedere se ci sono differenze.

Listener separati

E se sposti il lavoro in un listener di eventi separato? Aggiorna l'interfaccia utente 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);
});

Come si presenta ora nel riquadro del rendimento?

Diversi tipi di evento

La maggior parte delle interazioni attiva molti tipi di eventi, dal puntatore o eventi chiave, a eventi di passaggio del mouse, focus/sfocature 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 UI

Che 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. Elaborazione dei risultati dell'esperimento sulla durata in corso...

Traccia prestazioni: prima aggiorna l'UI

Visualizza il codice completo: ui_first.html

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

Osservando la registrazione nel riquadro Rendimento dei clic sul pulsante, puoi vedere che i risultati non sono cambiati. Mentre un aggiornamento dell'interfaccia utente veniva attivato prima del codice di blocco, il browser non ha aggiornato ciò che era visualizzato sullo schermo fino al completamento del listener di eventi, il che significa che il completamento dell'interazione ha comunque richiesto poco più di un secondo.

Un'interazione di un secondo ancora nel riquadro Rendimento

Traccia del rendimento: listener 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 da un punto di vista funzionale. L'interazione richiede ancora un intero secondo.

Se aumenti lo zoom nell'interazione del clic, puoi notare che vengono chiamate effettivamente due funzioni diverse come risultato dell'evento click.

Come previsto, la prima esecuzione, ovvero l'aggiornamento dell'UI, viene eseguita in modo estremamente rapido, mentre la seconda richiede un intero secondo. Tuttavia, la somma dei loro effetti si traduce nella stessa interazione lenta per l'utente finale.

In questo esempio, l'interazione di un secondo è ingrandita e mostra l'esecuzione della prima chiamata di funzione che richiede meno di un millisecondo

Analisi del rendimento: diversi tipi di eventi

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

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

Questi risultati sono molto simili. L'interazione è ancora di un secondo intero; l'unica differenza è che il listener click più breve solo per gli aggiornamenti dell'interfaccia utente viene ora eseguito dopo il listener pointerup di blocco.

In questo esempio, l'interazione di un secondo è ingrandita e mostra il completamento del listener di eventi di clic dopo meno di un millisecondo, dopo il listener puntatore su

Traccia del rendimento: nessun aggiornamento dell'interfaccia utente

Visualizza il codice completo: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • Il punteggio non si aggiorna, ma la pagina sì.
  • Animazioni, effetti CSS, azioni dei componenti web predefiniti (input modulo), inserimento di testo ed evidenziazione del testo continuano ad aggiornarsi.

In questo caso il pulsante passa allo stato attivo e torna allo stato attivo quando viene selezionato. Questa operazione richiede la visualizzazione 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 la visualizzazione della pagina, l'interazione richiede comunque un secondo intero.

La registrazione di un riquadro Rendimento mostra un'interazione praticamente identica a quelle precedenti.

Un'interazione di un secondo ancora nel riquadro Rendimento

Conclusione

Qualsiasi codice in esecuzione nel listener di eventi qualsiasi ritarda l'interazione.

  • Sono inclusi i listener registrati da diversi script e codice di framework o libreria che vengono eseguiti 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 semplice fatto che il codice non attivi una colorazione non significa che non sia in attesa del completamento da parte dei listener di eventi lenti.

7. Esperimento: ritardo di input

Come funziona il codice a lunga esecuzione al di fuori dei listener di eventi? Ad esempio:

  • Se hai un file <script> caricato in ritardo che ha bloccato in modo casuale la pagina durante il caricamento.
  • Una chiamata API, come setInterval, che blocca periodicamente la pagina?

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

Visualizza il codice completo: input_delay.html

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


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

Che cosa succede?

8. Inserimento dei risultati dell'esperimento sul ritardo

Visualizza il codice completo: input_delay.html

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


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

La registrazione di un clic su un pulsante che si verifica mentre l'attività di blocco setInterval era in esecuzione genera un'interazione di lunga durata, anche senza che l'interazione venga bloccata.

Questi periodi di lunga durata sono spesso chiamati attività lunghe.

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

Il riquadro Prestazioni di DevTools che mostra un&#39;attività di blocco di 1 secondo, un&#39;interazione che arriva a metà dell&#39;attività e un&#39;interazione di 642 millisecondi, attribuita principalmente al ritardo di input

Tieni presente che questo non influisce sempre sulle interazioni. Se non fai clic mentre l'attività è in esecuzione, potresti avere fortuna. Un modo "casuale" gli starnuti possono essere un incubo per il debug quando solo a volte causano problemi.

Un modo per individuarli è misurare le attività lunghe (o i frame di animazione lunghi) e il tempo di blocco totale.

9. Presentazione lenta

Finora, abbiamo esaminato le prestazioni di JavaScript, tramite il ritardo dell'input o i listener di eventi, ma quali altri fattori influisce sulla visualizzazione successiva del rendering?

Beh, aggiornare la pagina con effetti costosi!

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

Nel thread principale:

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

Fuori dal thread principale:

  • Utilizzo di CSS per potenziare gli effetti GPU
  • L'aggiunta di immagini molto grandi ad alta risoluzione
  • Utilizzare 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 ricrea l'intero DOM dopo aver fatto clic su un link, senza fermarsi per fornire un feedback visivo iniziale.
  • Una pagina di ricerca che offre filtri di ricerca complessi con un'interfaccia utente dinamica, ma richiede molto per eseguire questa azione.
  • Un pulsante di attivazione/disattivazione della modalità Buio che attiva lo stile/il layout per l'intera pagina

10. Esperimento: ritardo di presentazione

Dispositivo requestAnimationFrame lento

Simuliamo un lungo ritardo nella 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'esperimento sul ritardo nella presentazione

Visualizza il codice completo: presentation_delay.html

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

L'interazione dura ancora un secondo. Che cosa è successo?

requestAnimationFrame richiede un callback prima della colorazione successiva. Poiché INP misura il tempo che intercorre tra l'interazione e il disegno successivo, il valore blockFor(1000) in requestAnimationFrame continua a bloccare il disegno successivo per un secondo intero.

Un&#39;interazione di un secondo ancora nel riquadro Rendimento

Tuttavia, tieni presente due aspetti:

  • Al passaggio del mouse, vedrai tutto il tempo di interazione trascorso in "ritardo nella presentazione" poiché il blocco del thread principale si verifica dopo il ritorno del listener di eventi.
  • La radice dell'attività del thread principale non è più l'evento di clic, ma il "Frame di animazione attivato".

12. Diagnosi delle interazioni

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

Quando le interazioni durano a lungo, non è sempre chiaro quale sia il colpevole. È:

  • Ritardo di input?
  • Durata di elaborazione dell'evento?
  • Ritardo nella presentazione?

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

  1. Naviga sul web come faresti normalmente.
  2. (Facoltativo) Lascia aperta la console DevTools mentre l'estensione Web Vitals registra le interazioni.
  3. Se noti un'interazione di scarsa qualità, prova a ripeterla:
  • Se non puoi ripeterlo, utilizza i log della console per ottenere insight.
  • Se puoi ripeterlo, registralo nel riquadro delle prestazioni.

Tutti i ritardi

Prova ad aggiungere alla pagina alcuni di tutti questi problemi:

Guarda il codice completo: all_the_things.html

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

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

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

Quindi usa la console e il riquadro delle prestazioni per diagnosticare i problemi.

13. Esperimento: lavoro asincrono

Poiché è possibile 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 queste eventualmente aggiornano la pagina?

Se è possibile eseguire il rendering della verniciatura successiva dopo un'interazione, anche se il browser decide che non è necessario un nuovo aggiornamento del rendering, la misurazione dell'interazione viene interrotta.

Per fare una prova, continua ad aggiornare l'interfaccia utente dal listener di clic, ma esegui il blocco dopo il timeout.

Visualizza il codice completo: timeout_100.html

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

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

Che cosa succede ora?

14. Risultati degli esperimenti di lavoro asincroni

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 di durata successiva che si verifica più avanti nella traccia

Ora l'interazione è breve perché il thread principale è disponibile subito dopo l'aggiornamento dell'interfaccia utente. L'attività di blocco lunga è ancora in esecuzione, ma solo qualche tempo dopo il completamento, in modo che l'utente riceva un feedback immediato sull'interfaccia utente.

Lezione: se non riesci a rimuoverlo, fai almeno lo spostamento!

Metodi

Possiamo fare meglio di un valore fisso di 100 millisecondi setTimeout? Probabilmente vogliamo che il codice venga eseguito il più rapidamente possibile, altrimenti dovremmo rimuoverlo.

Obiettivo:

  • L'interazione verrà eseguita il giorno incrementAndUpdateUI().
  • blockFor() verrà eseguito il prima possibile, ma non blocca la colorazione successiva.
  • Questo si traduce in un comportamento prevedibile senza "timeout magici".

Ecco alcuni modi per raggiungere questo obiettivo:

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

&quot;requestPostAnimationFrame&quot;

A differenza di requestAnimationFrame da solo (che cercherà di essere eseguito prima della colorazione successiva e di solito genererà un'interazione lenta), requestAnimationFrame + setTimeout crea un semplice polyfill per requestPostAnimationFrame, con l'esecuzione del callback dopo la colorazione successiva.

Visualizza il codice completo: raf+task.html

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

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

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

Per quanto riguarda l'ergonomia, hai anche una promessa:

Consulta 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 irregolari)

Spostarsi su una pagina di blocco può essere d'aiuto, ma queste attività lunghe bloccano comunque la pagina, influenzando le interazioni future, così come molte altre animazioni e aggiornamenti della pagina.

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

Visualizza il codice completo: timeout_100.html

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

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

Che cosa succede se fai clic più volte velocemente?

Traccia prestazioni

Per ogni clic, viene messa in coda un'attività di un secondo, che garantisce che il thread principale sia bloccato per una quantità di tempo considerevole.

Più attività di seconda durata nel thread principale, con interazioni lente fino a 800 ms

Quando queste attività lunghe si sovrappongono a nuovi clic in arrivo, le interazioni sono lente anche se il listener di eventi stesso restituisce quasi immediatamente. Abbiamo creato la stessa situazione dell'esperimento precedente con ritardi nell'input. Solo questa volta, il ritardo di input non proviene da un setInterval, ma da un lavoro attivato da listener di eventi precedenti.

Strategie

Idealmente, vogliamo rimuovere completamente le attività lunghe.

  • Rimuovi completamente il codice non necessario, soprattutto gli script.
  • Ottimizza il codice per evitare di eseguire attività lunghe.
  • Interrompi il lavoro inattivo quando arrivano nuove interazioni.

16. Strategia 1: debounce

Una strategia classica. Ogni volta che le interazioni arrivano in rapida successione e gli effetti di elaborazione o di rete sono costosi, ritarda l'avvio del lavoro intenzionale per poter annullare e riavviare il processo. Questo pattern è utile per le interfacce utente, come i campi di completamento automatico.

  • Usa setTimeout per ritardare l'avvio di lavori costosi, con un timer, ad esempio da 500 a 1000 millisecondi.
  • In questo caso, salva l'ID del timer.
  • Se arriva una nuova interazione, annulla il timer precedente utilizzando clearTimeout.

Visualizza il codice completo: debounce.html

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

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

Traccia prestazioni

Sono diverse le interazioni, ma tutte le interazioni comprendono un&#39;unica e lunga attività di lavoro

Nonostante i numerosi clic, rimane in esecuzione una sola attività blockFor, che prima di eseguire l'esecuzione è che attende che non ci siano stati clic per un secondo intero. Per le interazioni a raffica, come la digitazione di un input di testo o target di elementi che dovrebbero ricevere più clic rapidi, questa è una strategia ideale da utilizzare per impostazione predefinita.

17. Strategia 2: interrompere il lavoro a lungo termine

C'è comunque la sfortuna possibilità che un altro clic venga attivato subito dopo che il periodo di debounce è trascorso, che venga atterrato nel mezzo di un'attività lunga e che diventi un'interazione molto lenta a causa del ritardo dell'input.

Idealmente, se nel corso dell'attività si verifica un'interazione, è consigliabile mettere in pausa il lavoro svolto in modo che tutte le nuove interazioni vengano gestite immediatamente. Come possiamo farlo?

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

Molti setTimeout s

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);
  });
});

Ciò avviene consentendo al browser di pianificare ogni attività singolarmente e l'input può avere una priorità maggiore.

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

Torniamo a cinque secondi completi di lavoro per cinque clic, ma ogni attività da un secondo per clic è stata suddivisa in dieci attività da 100 millisecondi. Di conseguenza, anche se più interazioni si sovrappongono alle attività in questione, nessuna interazione ha un ritardo di input superiore a 100 millisecondi. Il browser assegna la priorità ai listener di eventi in arrivo rispetto al lavoro setTimeout e le interazioni rimangono adattabili.

Questa strategia funziona particolarmente bene quando si pianificano punti di ingresso separati, ad esempio se si dispone di una serie di funzionalità indipendenti da chiamare al momento di caricamento dell'applicazione. Per impostazione predefinita, il semplice caricamento degli script e l'esecuzione di tutti gli elementi al momento della valutazione può consentire di eseguire tutto in un'attività molto lunga e molto lunga.

Tuttavia, questa strategia non è efficace per scomporre il codice ad alto accoppiamento, come nel caso di un loop for che utilizza lo stato condiviso.

Ora con yield()

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

Ad esempio:

Visualizza il codice completo: rendery.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 in precedenza, il thread principale viene restituito dopo un tratto di lavoro e il browser è in grado di rispondere a qualsiasi interazione in entrata, ma ora tutto ciò che serve è un await schedulerDotYield() invece di setTimeout separati, il che lo rende sufficientemente ergonomico da essere utilizzato anche nel bel mezzo di un loop for.

Ora con AbortContoller()

Ciò ha funzionato, ma ogni interazione pianifica più lavoro, anche se sono arrivate nuove interazioni e potrebbero aver modificato il lavoro da svolgere.

Con la strategia di antirimbalzo, abbiamo annullato il timeout precedente a ogni nuova interazione. Possiamo fare qualcosa di simile? Un modo per farlo è utilizzare una 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, viene avviato il loop blockInPiecesYieldyAborty for che esegue le operazioni necessarie, generando periodicamente il thread principale, in modo che il browser rimanga reattivo alle nuove interazioni.

Quando arriva un secondo clic, il primo loop viene contrassegnato come annullato con AbortController e viene avviato un nuovo ciclo blockInPiecesYieldyAborty: la prossima volta che viene pianificata l'esecuzione del primo loop, il sistema nota che signal.aborted è ora true e ritorna immediatamente senza ulteriori operazioni.

Il lavoro dei thread principali ora si svolge in tantissime piccole parti, le interazioni sono brevi e il lavoro dura solo il tempo necessario

18. Conclusione

Suddividere tutte le attività lunghe permette a un sito di reagire alle nuove interazioni. In questo modo puoi fornire rapidamente il tuo feedback iniziale e prendere decisioni, come interrompere la procedura in corso. A volte questo significa pianificare i punti di ingresso come attività separate. A volte, questo significa aggiungere "rendimento" in punti diversi, ove opportuno.

Va ricordato che

  • INP misura tutte le interazioni.
  • Ogni interazione viene misurata dall'input alla visualizzazione successiva, ovvero il modo in cui l'utente vede la reattività.
  • Il ritardo dell'input, la durata dell'elaborazione degli eventi e il ritardo nella presentazione influiscono tutti sulla reattività dell'interazione.
  • Puoi misurare facilmente l'INP e le suddivisioni delle interazioni con DevTools.

Strategie

  • Non avere codice di lunga durata (attività lunghe) nelle tue pagine.
  • Sposta il codice inutile dai listener di eventi fino al successivo colorazione.
  • Assicurati che l'aggiornamento del rendering sia efficace per il browser.

Scopri di più