1. Introduction
Une démo interactive et un atelier de programmation pour en savoir plus sur l'Interaction to Next Paint (INP).
Prérequis
- Connaissances en développement HTML et JavaScript
- Recommandation : consultez la documentation sur l'INP.
Objectifs
- Comment l'interaction entre les interactions utilisateur et la façon dont vous les gérez affecte la réactivité de la page.
- Comment réduire et éliminer les délais pour une expérience utilisateur fluide.
Ce dont vous avez besoin
- Un ordinateur capable de cloner du code depuis GitHub et d'exécuter des commandes npm.
- Un éditeur de texte.
- disposer d'une version récente de Chrome pour que toutes les mesures d'interaction fonctionnent.
2. Configuration
Obtenir et exécuter le code
Le code se trouve dans le dépôt web-vitals-codelabs
.
- Clonez le dépôt dans votre terminal :
git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
- Accédez au répertoire cloné :
cd web-vitals-codelabs/understanding-inp
- Installez les dépendances :
npm ci
- Démarrez le serveur Web :
npm run start
- Accédez à http://localhost:5173/understanding-inp/ dans votre navigateur.
Présentation de l'application
En haut de la page se trouvent un compteur Score et un bouton Incrémenter. Une démo classique de réactivité et de réactivité !
Quatre mesures sont affichées sous le bouton :
- INP : score INP actuel, qui correspond généralement à l'interaction la plus lente.
- Interaction : score de l'interaction la plus récente.
- FPS : nombre d'images par seconde du thread principal de la page.
- Timer : animation de minuteur en cours pour visualiser le jank.
Les entrées "FPS" et "Timer" ne sont pas du tout nécessaires pour mesurer les interactions. Ils sont ajoutés uniquement pour faciliter la visualisation de la réactivité.
Essayer
Essayez d'interagir avec le bouton Increment et regardez le score augmenter. Les valeurs INP et Interaction changent-elles à chaque incrément ?
L'INP mesure le temps qui s'écoule entre le moment où l'utilisateur interagit avec la page et celui où la page affiche réellement la mise à jour rendue à l'utilisateur.
3. Mesurer les interactions avec les outils pour les développeurs Chrome
Ouvrez les outils de développement depuis le menu Plus d'outils > Outils de développement, en effectuant un clic droit sur la page et en sélectionnant Inspecter, ou en utilisant un raccourci clavier.
Passez au panneau Performances, que vous utiliserez pour mesurer les interactions.
Ensuite, capturez une interaction dans le panneau "Performances".
- Appuyez sur "Enregistrer".
- Interagissez avec la page (appuyez sur le bouton Increment).
- Arrêtez l'enregistrement.
La chronologie qui s'affiche contient une piste Interactions. Développez-le en cliquant sur le triangle à gauche.
Deux interactions s'affichent. Faites un zoom avant sur la deuxième en faisant défiler l'écran ou en maintenant la touche W enfoncée.
En pointant sur l'interaction, vous pouvez voir qu'elle a été rapide, sans temps passé dans la durée de traitement, et avec un temps minimal dans le délai d'entrée et le délai de présentation, dont la durée exacte dépend de la vitesse de votre machine.
4. Écouteurs d'événements de longue durée
Ouvrez le fichier index.js
et supprimez la mise en commentaire de la fonction blockFor
dans l'écouteur d'événements.
Voir le code complet : click_block.html
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
});
Enregistrez le fichier. Le serveur verra la modification et actualisera la page pour vous.
Essayez d'interagir de nouveau avec la page. Les interactions seront désormais sensiblement plus lentes.
Trace de performances
Effectuez un autre enregistrement dans le panneau "Performances" pour voir à quoi cela ressemble.
Ce qui était auparavant une brève interaction prend désormais une seconde entière.
Lorsque vous pointez sur l'interaction, vous remarquerez que le temps est presque entièrement consacré à la "Durée de traitement", qui correspond au temps nécessaire pour exécuter les rappels du gestionnaire d'événements. Étant donné que l'appel blockFor
bloquant se trouve entièrement dans l'écouteur d'événements, c'est là que le temps s'écoule.
5. Test : durée du traitement
Essayez de réorganiser le travail des écouteurs d'événements pour voir l'effet sur l'INP.
Mettre à jour l'UI en premier
Que se passe-t-il si vous inversez l'ordre des appels JS (mise à jour de l'UI, puis blocage) ?
Voir le code complet : ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
Avez-vous remarqué que l'UI s'est affichée plus tôt ? L'ordre a-t-il une incidence sur les scores INP ?
Essayez de faire un traçage et d'examiner l'interaction pour voir s'il y a des différences.
Séparer les écouteurs
Que se passe-t-il si vous déplacez le travail vers un écouteur d'événements distinct ? Mettez à jour l'UI dans un écouteur d'événements et bloquez la page à partir d'un autre écouteur.
Voir le code complet : two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
À quoi ressemble-t-il désormais dans le panneau "Performances" ?
Différents types d'événements
La plupart des interactions déclenchent de nombreux types d'événements, qu'il s'agisse d'événements de pointeur ou de clavier, ou d'événements de survol, de focus/blur et d'événements synthétiques tels que "beforechange" et "beforeinput".
De nombreuses pages réelles comportent des écouteurs pour différents événements.
Que se passe-t-il si vous modifiez les types d'événements pour les écouteurs d'événements ? Par exemple, remplacer l'un des écouteurs d'événements click
par pointerup
ou mouseup
?
Voir le code complet : diff_handlers.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
Aucune mise à jour de l'UI
Que se passe-t-il si vous supprimez l'appel à la mise à jour de l'UI de l'écouteur d'événements ?
Voir le code complet : no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
6. Résultats des tests de durée de traitement
Trace de performances : mettre à jour l'UI en premier
Voir le code complet : ui_first.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
blockFor(1000);
});
Si vous examinez l'enregistrement du panneau "Performances" lorsque vous cliquez sur le bouton, vous constaterez que les résultats n'ont pas changé. Bien qu'une mise à jour de l'UI ait été déclenchée avant le code bloquant, le navigateur n'a pas réellement mis à jour ce qui était affiché à l'écran avant la fin de l'écouteur d'événement. Cela signifie que l'interaction a quand même pris un peu plus d'une seconde.
Trace de performances : écouteurs séparés
Voir le code complet : two_click.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('click', () => {
blockFor(1000);
});
Une fois encore, il n'y a aucune différence fonctionnelle. L'interaction prend toujours une seconde entière.
Si vous zoomez sur l'interaction de clic, vous verrez que deux fonctions différentes sont appelées à la suite de l'événement click
.
Comme prévu, la première (mise à jour de l'UI) s'exécute très rapidement, tandis que la seconde prend une seconde entière. Toutefois, la somme de leurs effets entraîne la même lenteur d'interaction pour l'utilisateur final.
Tracé des performances : différents types d'événements
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
button.addEventListener('pointerup', () => {
blockFor(1000);
});
Ces résultats sont très similaires. L'interaction dure toujours une seconde entière. La seule différence est que l'écouteur click
plus court, qui ne concerne que la mise à jour de l'UI, s'exécute désormais après l'écouteur pointerup
bloquant.
Trace de performances : aucune mise à jour de l'UI
Voir le code complet : no_ui.html
button.addEventListener('click', () => {
blockFor(1000);
// score.incrementAndUpdateUI();
});
- Le score ne se met pas à jour, mais la page, si !
- Les animations, les effets CSS, les actions par défaut des composants Web (saisie de formulaire), la saisie de texte et la mise en surbrillance de texte continuent de se mettre à jour.
Dans ce cas, le bouton passe à l'état actif, puis revient à l'état inactif lorsqu'on clique dessus. Le navigateur doit donc effectuer un rendu, ce qui signifie qu'il y a toujours un temps d'interaction avec la page.
Étant donné que le gestionnaire d'événements a bloqué le thread principal pendant une seconde, empêchant la page d'être peinte, l'interaction prend toujours une seconde entière.
L'enregistrement du panneau "Performances" montre une interaction pratiquement identique à celles qui l'ont précédée.
Plats à emporter
Tout code exécuté dans n'importe quel écouteur d'événements retardera l'interaction.
- Cela inclut les écouteurs enregistrés à partir de différents scripts et du code de framework ou de bibliothèque qui s'exécute dans les écouteurs, comme une mise à jour d'état qui déclenche le rendu d'un composant.
- Non seulement votre propre code, mais aussi tous les scripts tiers.
C'est un problème courant.
Enfin, ce n'est pas parce que votre code ne déclenche pas de peinture qu'une peinture n'attendra pas la fin des écouteurs d'événements lents.
7. Experiment: input delay
Qu'en est-il du code de longue durée en dehors des écouteurs d'événements ? Exemple :
- Si vous aviez un
<script>
à chargement tardif qui bloquait la page de manière aléatoire pendant le chargement. - Un appel d'API, tel que
setInterval
, qui bloque périodiquement la page ?
Essayez de supprimer blockFor
de l'écouteur d'événements et de l'ajouter à un setInterval()
:
Voir le code complet : input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
Que se passe-t-il ?
8. Résultats du test de délai de réponse à l'entrée utilisateur
Voir le code complet : input_delay.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
});
L'enregistrement d'un clic sur un bouton qui se produit pendant l'exécution de la tâche de blocage setInterval
entraîne une interaction de longue durée, même si aucun travail de blocage n'est effectué dans l'interaction elle-même.
Ces périodes de longue durée sont souvent appelées tâches longues.
Si vous pointez sur l'interaction dans les outils pour les développeurs, vous verrez que la durée de l'interaction est désormais principalement attribuée au délai d'entrée, et non à la durée de traitement.
Notez que cela n'a pas toujours d'incidence sur les interactions. Si vous ne cliquez pas lorsque la tâche est en cours d'exécution, vous aurez peut-être de la chance. Ces éternuements "aléatoires" peuvent être un cauchemar à déboguer lorsqu'ils ne causent des problèmes que de temps en temps.
Pour les identifier, vous pouvez mesurer les tâches longues (ou Long Animation Frames) et le temps de blocage total.
9. Présentation lente
Jusqu'à présent, nous avons examiné les performances de JavaScript, via le délai d'entrée ou les écouteurs d'événements. Mais qu'est-ce qui affecte le rendu du prochain affichage ?
En mettant à jour la page avec des effets coûteux.
Même si la page est mise à jour rapidement, le navigateur peut avoir du mal à l'afficher.
Sur le thread principal :
- Frameworks d'UI qui doivent afficher les mises à jour après les changements d'état
- Les modifications du DOM ou l'activation/désactivation de nombreux sélecteurs de requête CSS coûteux peuvent déclencher de nombreux événements de style, de mise en page et de peinture.
Hors du thread principal :
- Utiliser le CSS pour alimenter les effets GPU
- Ajouter des images haute résolution très volumineuses
- Utiliser SVG/Canvas pour dessiner des scènes complexes
Voici quelques exemples courants sur le Web :
- Un site SPA qui reconstruit l'intégralité du DOM après avoir cliqué sur un lien, sans s'arrêter pour fournir un retour visuel initial.
- Une page de recherche qui propose des filtres de recherche complexes avec une interface utilisateur dynamique, mais qui exécute des écouteurs coûteux pour ce faire.
- Un bouton d'activation du mode sombre qui déclenche le style/la mise en page pour l'ensemble de la page
10. Test : délai de présentation
Périphérique requestAnimationFrame
lent
Simulons un long délai de présentation à l'aide de l'API requestAnimationFrame()
.
Déplacez l'appel blockFor
dans un rappel requestAnimationFrame
afin qu'il s'exécute après le retour de l'écouteur d'événements :
Voir le code complet : presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
Que se passe-t-il ?
11. Résultats des tests de délai de présentation
Voir le code complet : presentation_delay.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
L'interaction dure toujours une seconde. Que s'est-il passé ?
requestAnimationFrame
demande un rappel avant le prochain affichage. Étant donné que l'INP mesure le temps écoulé entre l'interaction et le prochain affichage, le blockFor(1000)
dans requestAnimationFrame
continue de bloquer le prochain affichage pendant une seconde entière.
Toutefois, notez deux choses :
- Au survol, vous verrez que tout le temps d'interaction est désormais consacré au "délai de présentation", car le blocage du thread principal se produit après le retour du gestionnaire d'événements.
- La racine de l'activité du thread principal n'est plus l'événement de clic, mais "Animation Frame Fired" (Frame d'animation déclenché).
12. Diagnostiquer les interactions
Sur cette page de test, la réactivité est très visuelle, avec les scores, les minuteurs et l'interface utilisateur du compteur. En revanche, elle est plus subtile sur la page de test de la moyenne.
Lorsque les interactions durent longtemps, il n'est pas toujours évident de savoir quelle en est la cause. Est-ce :
- Délai de réponse à l'entrée utilisateur ?
- Quelle est la durée de traitement des événements ?
- Délai de présentation ?
Sur n'importe quelle page, vous pouvez utiliser les outils de développement pour mesurer la réactivité. Pour prendre cette habitude, essayez le flux suivant :
- Parcourez le Web comme d'habitude.
- Surveillez le journal des interactions dans la vue des métriques en direct du panneau "Performances" des outils de développement.
- Si vous constatez une interaction peu performante, essayez de la répéter :
- Si vous ne parvenez pas à le reproduire, utilisez le journal des interactions pour obtenir des informations.
- Si vous pouvez le reproduire, enregistrez une trace dans le panneau "Performances".
Tous les retards
Essayez d'ajouter un peu de chacun de ces problèmes à la page :
Voir le code complet : all_the_things.html
setInterval(() => {
blockFor(1000);
}, 3000);
button.addEventListener('click', () => {
blockFor(1000);
score.incrementAndUpdateUI();
requestAnimationFrame(() => {
blockFor(1000);
});
});
Utilisez ensuite la console et le panneau des performances pour diagnostiquer les problèmes.
13. Test : travail asynchrone
Étant donné que vous pouvez démarrer des effets non visuels dans les interactions, comme effectuer des requêtes réseau, démarrer des minuteurs ou simplement mettre à jour l'état global, que se passe-t-il lorsque ces effets mettent finalement à jour la page ?
Tant que le next paint après une interaction est autorisé à s'afficher, même si le navigateur décide qu'il n'a pas réellement besoin d'une nouvelle mise à jour de rendu, la mesure de l'interaction s'arrête.
Pour essayer cela, continuez à mettre à jour l'UI à partir de l'écouteur de clics, mais exécutez le travail bloquant à partir du délai avant expiration.
Voir le code complet : timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
Que va-t-il se passer ?
14. Résultats du test de tâches asynchrones
Voir le code complet : timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
L'interaction est désormais courte, car le thread principal est disponible immédiatement après la mise à jour de l'UI. La tâche de blocage de longue durée s'exécute toujours, mais un peu après l'affichage. L'utilisateur reçoit donc un retour immédiat de l'UI.
Leçon : si vous ne pouvez pas le supprimer, au moins déplacez-le !
Méthodes
Peut-on faire mieux qu'un setTimeout
fixe de 100 millisecondes ? Nous souhaitons probablement toujours que le code s'exécute le plus rapidement possible, sinon nous l'aurions simplement supprimé.
Objectif :
- L'interaction s'exécutera
incrementAndUpdateUI()
. blockFor()
s'exécutera dès que possible, mais ne bloquera pas la prochaine mise à jour de l'affichage.- Cela permet d'obtenir un comportement prévisible sans "délai magique".
Voici quelques exemples :
setTimeout(0)
Promise.then()
requestAnimationFrame
requestIdleCallback
scheduler.postTask()
"requestPostAnimationFrame"
Contrairement à requestAnimationFrame
seul (qui tentera de s'exécuter avant le prochain affichage et qui entraînera généralement une interaction lente), requestAnimationFrame
+ setTimeout
constitue un simple polyfill pour requestPostAnimationFrame
, exécutant le rappel après le prochain affichage.
Voir le code complet : raf+task.html
function afterNextPaint(callback) {
requestAnimationFrame(() => {
setTimeout(callback, 0);
});
}
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
afterNextPaint(() => {
blockFor(1000);
});
});
Pour plus d'ergonomie, vous pouvez même l'encapsuler dans une promesse :
Voir le code complet : raf+task2.html
async function nextPaint() {
return new Promise(resolve => afterNextPaint(resolve));
}
button.addEventListener('click', async () => {
score.incrementAndUpdateUI();
await nextPaint();
blockFor(1000);
});
15. Interactions multiples (et clics de rage)
Déplacer les tâches de blocage longues peut aider, mais ces tâches longues bloquent toujours la page, ce qui affecte les interactions futures ainsi que de nombreuses autres animations et mises à jour de la page.
Essayez à nouveau la version de la page avec blocage asynchrone (ou la vôtre si vous avez trouvé votre propre variante pour différer le travail à la dernière étape) :
Voir le code complet : timeout_100.html
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
setTimeout(() => {
blockFor(1000);
}, 100);
});
Que se passe-t-il si vous cliquez plusieurs fois rapidement ?
Trace de performances
Pour chaque clic, une tâche d'une seconde est mise en file d'attente, ce qui garantit que le thread principal est bloqué pendant une durée considérable.
Lorsque ces tâches longues se chevauchent avec de nouveaux clics, les interactions sont lentes, même si l'écouteur d'événements lui-même renvoie une réponse presque immédiatement. Nous avons créé la même situation que dans l'expérience précédente avec les délais d'entrée. Cette fois-ci, le délai d'entrée ne provient pas d'un setInterval
, mais d'un travail déclenché par des écouteurs d'événements précédents.
Stratégies
Dans l'idéal, nous souhaitons supprimer complètement les tâches longues.
- Supprimez complètement le code inutile, en particulier les scripts.
- Optimisez le code pour éviter d'exécuter des tâches longues.
- Abandonnez les tâches obsolètes lorsque de nouvelles interactions arrivent.
16. Stratégie 1 : Débounce
Une stratégie classique. Chaque fois que des interactions arrivent rapidement les unes après les autres et que les effets de traitement ou de réseau sont coûteux, retardez volontairement le démarrage du travail afin de pouvoir l'annuler et le redémarrer. Ce modèle est utile pour les interfaces utilisateur telles que les champs de saisie semi-automatique.
- Utilisez
setTimeout
pour retarder le début des tâches coûteuses, avec un minuteur, peut-être de 500 à 1 000 millisecondes. - Enregistrez l'ID du minuteur lorsque vous le faites.
- Si une nouvelle interaction arrive, annulez le minuteur précédent à l'aide de
clearTimeout
.
Voir le code complet : debounce.html
let timer;
button.addEventListener('click', () => {
score.incrementAndUpdateUI();
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
blockFor(1000);
}, 1000);
});
Trace de performances
Malgré plusieurs clics, une seule tâche blockFor
finit par s'exécuter, en attendant qu'il n'y ait plus de clics pendant une seconde entière avant de s'exécuter. Pour les interactions qui se produisent par rafales (comme la saisie dans un champ de texte ou les cibles d'éléments qui sont censées recevoir plusieurs clics rapides), il s'agit d'une stratégie idéale à utiliser par défaut.
17. Stratégie 2 : interrompre les tâches de longue durée
Il existe toujours un risque que l'utilisateur clique à nouveau juste après la période d'antirebond, au milieu de la longue tâche, ce qui rend l'interaction très lente en raison du délai d'entrée.
Idéalement, si une interaction se produit au milieu de notre tâche, nous voulons mettre en pause notre travail afin que toute nouvelle interaction soit traitée immédiatement. Comment faire ?
Il existe des API comme isInputPending
, mais il est généralement préférable de diviser les longues tâches en plusieurs parties.
Beaucoup de setTimeout
Première tentative : faites quelque chose de simple.
Voir le code complet : 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);
});
});
Pour ce faire, le navigateur peut planifier chaque tâche individuellement, et les entrées peuvent avoir une priorité plus élevée.
Nous revenons à cinq secondes de travail pour cinq clics, mais chaque tâche d'une seconde par clic a été divisée en dix tâches de 100 millisecondes. Par conséquent, même si plusieurs interactions se chevauchent avec ces tâches, aucune interaction n'a de délai d'entrée supérieur à 100 millisecondes. Le navigateur donne la priorité aux écouteurs d'événements entrants par rapport au travail setTimeout
, et les interactions restent réactives.
Cette stratégie fonctionne particulièrement bien lorsque vous planifiez des points d'entrée distincts, par exemple si vous avez un ensemble de fonctionnalités indépendantes que vous devez appeler au moment du chargement de l'application. Le simple chargement de scripts et l'exécution de tout au moment de l'évaluation des scripts peuvent exécuter tout dans une longue tâche géante par défaut.
Toutefois, cette stratégie ne fonctionne pas aussi bien pour séparer le code étroitement couplé, comme une boucle for
qui utilise un état partagé.
Maintenant avec yield()
Toutefois, nous pouvons tirer parti des async
et await
modernes pour ajouter facilement des "points de rendement" à n'importe quelle fonction JavaScript.
Exemple :
Voir le code complet : 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);
});
Comme auparavant, le thread principal est cédé après un bloc de travail et le navigateur peut répondre à toute interaction entrante. Toutefois, il suffit désormais d'un await schedulerDotYield()
au lieu de setTimeout
distincts, ce qui le rend suffisamment ergonomique pour être utilisé même au milieu d'une boucle for
.
Maintenant avec AbortContoller()
Cela a fonctionné, mais chaque interaction planifie plus de travail, même si de nouvelles interactions sont arrivées et ont pu modifier le travail à effectuer.
Avec la stratégie de suppression des rebonds, nous avons annulé le délai d'attente précédent à chaque nouvelle interaction. Peut-on faire quelque chose de similaire ici ? Pour ce faire, vous pouvez utiliser un AbortController()
:
Voir le code complet : 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);
});
Lorsqu'un clic est effectué, il lance la boucle blockInPiecesYieldyAborty
for
, qui effectue le travail nécessaire tout en cédant périodiquement le thread principal afin que le navigateur reste réactif aux nouvelles interactions.
Lorsqu'un deuxième clic est effectué, la première boucle est marquée comme annulée avec AbortController
et une nouvelle boucle blockInPiecesYieldyAborty
est démarrée. La prochaine fois que la première boucle est programmée pour s'exécuter à nouveau, elle remarque que signal.aborted
est maintenant true
et renvoie immédiatement sans effectuer d'autres tâches.
18. Conclusion
La découpe de toutes les tâches longues permet à un site de répondre aux nouvelles interactions. Cela vous permet de fournir rapidement des commentaires initiaux et de prendre des décisions, comme interrompre un travail en cours. Cela signifie parfois planifier les points d'entrée comme des tâches distinctes. Parfois, cela signifie ajouter des points de rendement là où c'est pratique.
À noter
- L'INP mesure toutes les interactions.
- Chaque interaction est mesurée de l'entrée à la prochaine peinture, c'est-à-dire la façon dont l'utilisateur perçoit la réactivité.
- Le délai de réponse à l'entrée utilisateur, la durée de traitement des événements et le délai de présentation ont tous un impact sur la réactivité des interactions.
- Vous pouvez facilement mesurer l'INP et les détails des interactions avec les outils de développement.
Stratégies
- N'incluez pas de code de longue durée (tâches longues) sur vos pages.
- Déplacez le code inutile hors des écouteurs d'événements jusqu'à la prochaine peinture.
- Assurez-vous que la mise à jour du rendu elle-même est efficace pour le navigateur.