Ajouter de l'interactivité avec JavaScript

Ilya Grigorik
Ilya Grigorik

Publié le 31 décembre 2013

JavaScript nous permet de modifier presque tous les aspects de la page : contenu, style et réponse aux interactions utilisateur. Toutefois, JavaScript peut également bloquer la construction DOM et retarder le rendu de la page. Pour obtenir des performances optimales, rendez votre code JavaScript asynchrone et éliminez tout code JavaScript inutile du chemin critique du rendu.

  • JavaScript peut interroger et modifier le DOM et le CSSOM.
  • L'exécution JavaScript bloque sur le CSSOM.
  • JavaScript bloque la création du DOM, sauf si elle est explicitement déclarée comme asynchrone.

JavaScript est un langage dynamique qui s'exécute dans un navigateur et nous permet de modifier presque tous les aspects du comportement de la page : nous pouvons modifier le contenu en ajoutant et en supprimant des éléments de l'arborescence DOM, nous pouvons modifier les propriétés CSSOM de chaque élément, nous pouvons gérer les entrées utilisateur, et bien plus encore. Pour illustrer ce point, voyons ce qui se passe lorsque l'exemple "Hello World" précédent est modifié pour ajouter un court script intégré :

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline'; // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>

Essayer

  • JavaScript nous permet d'accéder au DOM et d'extraire la référence au nœud de span masqué. Le nœud n'est peut-être pas visible dans l'arborescence de rendu, mais il est toujours présent dans le DOM. Une fois la référence obtenue, nous pouvons modifier son texte (via .textContent) et même remplacer la propriété de style d'affichage calculée "none" par "inline". Notre page affiche désormais Bonjour, élèves interactifs !.

  • JavaScript nous permet également de créer, de styliser, d'ajouter et de supprimer de nouveaux éléments dans le DOM. Techniquement, l'intégralité de notre page pourrait n'être qu'un seul grand fichier JavaScript qui crée et stylise les éléments un par un. Bien que cela fonctionne, en pratique, il est beaucoup plus simple d'utiliser HTML et CSS. Dans la deuxième partie de notre fonction JavaScript, nous créons un élément div, définissons son contenu textuel, lui appliquons un style et l'ajoutons au corps.

Aperçu d&#39;une page affichée sur un appareil mobile.

Nous avons ainsi modifié le contenu et le style CSS d'un nœud DOM existant, et ajouté un nœud entièrement nouveau au document. Notre page ne remportera pas de prix de conception, mais elle illustre la puissance et la flexibilité de JavaScript.

Cependant, bien que JavaScript offre de nombreuses fonctionnalités, il crée de nombreuses limitations supplémentaires sur la façon et le moment où la page est affichée.

Tout d'abord, notez que dans l'exemple précédent, notre script intégré se trouve en bas de la page. Pourquoi ? Vous devriez essayer vous-même, mais si nous déplaçons le script au-dessus de l'élément <span>, vous remarquerez qu'il échoue et qu'il ne trouve aucune référence aux éléments <span> du document. Autrement dit, getElementsByTagName('span') renvoie null. Cela démontre une propriété importante : notre script est exécuté au point exact où il est inséré dans le document. Lorsque l'analyseur HTML rencontre une balise de script, il suspend le processus de création du DOM et cède le contrôle au moteur JavaScript. Une fois le moteur JavaScript terminé, le navigateur reprend là où il s'était arrêté et reprend la création du DOM.

En d'autres termes, notre bloc de script ne peut trouver aucun élément plus loin sur la page, car ils n'ont pas encore été traités. Autrement dit, l'exécution de notre script intégré bloque la création du DOM, ce qui retarde également le rendu initial.

L'ajout de scripts dans une page présente également une autre caractéristique subtile : ils peuvent lire et modifier non seulement le DOM, mais aussi les propriétés CSSOM. C'est exactement ce que nous faisons dans notre exemple lorsque nous modifions la propriété d'affichage de l'élément span de "none" à "inline". Quelles conséquences cela a-t-il ? Nous avons maintenant une condition de concurrence.

Que se passe-t-il si le navigateur n'a pas fini de télécharger et de compiler le CSSOM au moment où nous voulons exécuter notre script ? La réponse n'est pas très bonne en termes de performances: le navigateur retarde l'exécution du script et la construction du DOM jusqu'à ce qu'il ait terminé de télécharger et de créer le CSSOM.

En résumé, JavaScript introduit de nombreuses nouvelles dépendances entre le DOM, le CSSOM et l'exécution JavaScript. Cela peut entraîner des retards importants dans le traitement et l'affichage de la page à l'écran :

  • L'emplacement du script dans le document est important.
  • Lorsque le navigateur rencontre une balise de script, la création du DOM est suspendue jusqu'à la fin de l'exécution du script.
  • JavaScript peut interroger et modifier le DOM et le CSSOM.
  • L'exécution JavaScript est mise en pause jusqu'à ce que le CSSOM soit prêt.

Dans une large mesure, "optimiser le chemin de rendu critique" signifie comprendre et optimiser le graphique des dépendances entre HTML, CSS et JavaScript.

Blocage de l'analyseur par rapport au JavaScript asynchrone

Par défaut, l'exécution JavaScript est "bloquante pour l'analyseur" : lorsque le navigateur rencontre un script dans le document, il doit suspendre la création du DOM, céder le contrôle à l'environnement d'exécution JavaScript et laisser le script s'exécuter avant de poursuivre la création du DOM. Nous l'avons vu à l'œuvre avec un script intégré dans notre exemple précédent. En fait, les scripts intégrés sont toujours bloquants pour l'analyseur, sauf si vous écrivez du code supplémentaire pour différer leur exécution.

Qu'en est-il des scripts inclus à l'aide d'une balise de script ? Prenons l'exemple précédent et extrayons le code dans un fichier distinct :

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline'; // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

Essayer

Que vous utilisiez une balise <script> ou un extrait JavaScript intégré, vous vous attendez à ce que les deux se comportent de la même manière. Dans les deux cas, le navigateur met en pause et exécute le script avant de pouvoir traiter le reste du document. Toutefois, dans le cas d'un fichier JavaScript externe, le navigateur doit s'arrêter pour attendre que le script soit récupéré à partir du disque, du cache ou d'un serveur distant, ce qui peut ajouter un retard de plusieurs dizaines à milliers de millisecondes au chemin critique du rendu.

Par défaut, tout le code JavaScript est bloqué par l'analyseur. Comme le navigateur ne sait pas ce que le script prévoit de faire sur la page, il part du pire et bloque l'analyseur. L'envoi d'un signal au navigateur indiquant que le script n'a pas besoin d'être exécuté au point exact où il est référencé permet au navigateur de continuer à construire le DOM et de laisser le script s'exécuter lorsqu'il est prêt, par exemple après l'extraction du fichier à partir du cache ou d'un serveur distant.

Pour ce faire, l'attribut async est ajouté à l'élément <script> :

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <link href="style.css" rel="stylesheet" />
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg" /></div>
    <script src="app.js" async></script>
  </body>
</html>

Essayer

L'ajout du mot clé asynchrone au tag de script indique au navigateur de ne pas bloquer la construction DOM en attendant que le script soit disponible. Cela peut améliorer considérablement les performances.

Commentaires