Pour mesurer le texte du DOM de manière fiable, attendez la feuille de style concernée, placez le texte dans la mise en page prévue, puis attendez document.fonts.ready avant de lire la géométrie. Ce guide présente la séquence dans un navigateur de bureau sur Mac et explique quand la répéter.

La tâche consiste ici à mesurer un libellé synthétique ou un paragraphe avec retour à la ligne dans une page de navigateur, par exemple pour déterminer l’espace nécessaire à une carte. Le navigateur doit effectuer la mise en page du texte ; votre code doit établir que tout est prêt, puis lire les blocs obtenus.

Quel signal de disponibilité faut-il attendre ?

La construction du document, le chargement des feuilles de style et la mise en page des polices correspondent à des étapes distinctes. Choisissez l’étape qui correspond au travail que vous contrôlez, puis mesurez une fois le contenu et ses styles en place.

Signal Ce qu’il garantit Ce qui reste à vérifier
DOMContentLoaded L’analyse du document et l’exécution des scripts différés sont terminées Il n’attend pas directement les feuilles de style
L’événement load d’une feuille de style Cette feuille de style et son contenu importé ont été chargés et analysés Le chargement des polices et la mise en page qui en résulte pour l’élément cible
L’événement load de la fenêtre Les ressources initiales de la page, y compris les feuilles de style, ont été chargées, à l’exception des ressources différées Les modifications ultérieures de l’application et la disponibilité des polices
document.fonts.ready Le chargement et la mise en page utilisant les polices du document sont terminés Les changements ultérieurs de contenu, de style ou de dimensions

MDN explique la nuance concernant les feuilles de style : DOMContentLoaded ne les attend pas directement, mais les scripts différés les attendent, ce qui peut donc retarder indirectement l’événement. Cet événement est utile pour trouver des éléments, mais ne suffit pas à garantir que la mesure est prête. Document : événement DOMContentLoaded.

Pour une feuille de style que vous insérez vous-même, associez ses gestionnaires load et error avant d’insérer le lien. MDN indique que l’événement load de la feuille de style survient après son chargement et son analyse, juste avant que les styles commencent à s’appliquer. Le traitement des feuilles de style défini par WHATWG distingue également le chargement réussi de l’événement error. Événements de chargement des feuilles de style, Norme HTML : liens vers des feuilles de style.

Pour le chargement initial des ressources d’une page, l’événement load de la fenêtre constitue un point de contrôle plus général. Un script qui démarre après doit vérifier document.readyState avant de s’abonner, car un événement déjà déclenché ne provoquera pas l’exécution d’un gestionnaire ajouté par la suite. Si le code de l’application insère ensuite une feuille de style, il doit quand même suivre explicitement cette insertion. Window : événement load, Vérifier l’état de chargement du document.

Que garantit réellement la disponibilité des polices ?

La spécification W3C CSS Font Loading indique que ready se résout une fois les opérations de mise en page concernées terminées et lorsqu’aucun autre chargement de police n’est nécessaire pour ce travail. Cela inclut les cas où une police de remplacement entraîne des chargements supplémentaires. Cette même promesse ne se résout qu’une seule fois, et des changements ultérieurs peuvent provoquer d’autres chargements de polices. Attendez document.fonts.ready après les changements pertinents pour chaque mesure au lieu de conserver indéfiniment une unique promesse lancée au démarrage. Module CSS Font Loading niveau 3 : attribut ready.

La disponibilité concerne les polices utilisées par le document. Elle ne signifie pas que toutes les polices déclarées ont été téléchargées. MDN précise que font-display: optional peut faire différer l’ensemble des polices utilisées de celui des polices déclarées. Une mesure terminée peut donc décrire correctement le texte affiché avec une police de remplacement. Document : propriété fonts.

Déterminez le résultat dont vous avez besoin avant d’écrire le code d’attente :

  • Mesurer le résultat actuellement affiché : attendez que les polices soient prêtes et lisez la géométrie du DOM, en acceptant les choix de remplacement du navigateur.
  • Exiger une police déclarée téléchargeable : chargez explicitement la police correspondante, gérez les échecs, puis attendez que les polices du document soient prêtes avant la mesure.
  • Mesurer un contenu qui change : répétez la séquence après chaque mise à jour pertinente et ignorez les résultats remplacés par une autre mise à jour.

Pour le deuxième cas, document.fonts.load() accepte une spécification de police CSS et un texte d’exemple facultatif. Avec une famille synthétique déclarée sous le nom SampleFace, la spécification pourrait être 400 18px \"SampleFace\", et le texte pourrait être Panel Alpha. La promesse renvoyée est rejetée si une police correspondante ne parvient pas à se charger. L’argument texte sélectionne les polices selon leurs plages Unicode déclarées ; il ne vérifie pas la prise en charge de chaque glyphe. FontFaceSet : méthode load().

Ne remplacez pas cette exigence par document.fonts.check(). MDN explique que cette méthode peut renvoyer true même si la police demandée est indisponible. De même, selon la spécification W3C, ready n’est jamais rejetée : sa résolution ne prouve donc pas à elle seule que le chargement de la police a réussi. FontFaceSet : méthode check(), CSS Font Loading : attribut ready.

Exécuter un exemple complet, du chargement de la feuille de style à la mesure

Sur votre Mac, ouvrez about:blank dans un navigateur de bureau récent, ouvrez les outils de développement et sélectionnez Console. Exécutez une fois le programme JavaScript complet ci-dessous. Il crée son propre texte d’exemple neutre et sa propre feuille de style ; aucun serveur, aucune bibliothèque ni aucun fichier de police ne sont nécessaires.

L’exemple utilise volontairement system-ui. Il présente la séquence d’attente sans dépendre d’une ressource téléchargeable. Sur une page qui utilise des polices Web, conservez la séquence et laissez les déclarations de polices de cette page fournir les ressources. L’exemple de navigateur a été examiné, mais n’a pas été exécuté pour ce manuscrit.

(async () => {
  if (!document.fonts || !document.body) {
    throw new Error("A document body and CSS Font Loading support are required.");
  }
  if (document.getElementById("text-measure-demo")) {
    throw new Error("Reload the blank tab before running this example again.");
  }

  async function withDeadline(promise, label, milliseconds = 10000) {
    let timer;
    try {
      return await Promise.race([
        promise,
        new Promise((_, reject) => {
          timer = setTimeout(() => {
            reject(new Error(`${label} did not finish within the deadline.`));
          }, milliseconds);
        })
      ]);
    } finally {
      clearTimeout(timer);
    }
  }

  const paragraph = document.createElement("p");
  paragraph.id = "text-measure-demo";
  paragraph.textContent =
    "Panel Alpha contains a compact label. Panel Beta contains another label.";
  document.body.append(paragraph);

  const css = `
    #text-measure-demo {
      box-sizing: border-box;
      width: 280px;
      margin: 24px;
      padding: 12px;
      border: 1px solid #777;
      font: 400 18px/1.5 system-ui, sans-serif;
      letter-spacing: normal;
      white-space: normal;
      overflow-wrap: normal;
    }
  `;
  const stylesheetURL = URL.createObjectURL(
    new Blob([css], { type: "text/css" })
  );
  const link = document.createElement("link");
  link.rel = "stylesheet";
  link.href = stylesheetURL;

  const stylesheetLoaded = new Promise((resolve, reject) => {
    link.onload = resolve;
    link.onerror = () => reject(new Error("The sample stylesheet failed."));
  });

  try {
    document.head.append(link);
    await withDeadline(stylesheetLoaded, "Stylesheet loading");

    // Establish the target's styled layout before reading font readiness.
    paragraph.getBoundingClientRect();
    await withDeadline(document.fonts.ready, "Font readiness");

    const style = getComputedStyle(paragraph);
    const range = document.createRange();
    range.selectNodeContents(paragraph);
    const copyRect = rect => ({
      x: rect.x, y: rect.y, width: rect.width, height: rect.height
    });

    console.log({
      text: paragraph.textContent,
      fontFamily: style.fontFamily,
      fontSize: style.fontSize,
      lineHeight: style.lineHeight,
      elementBox: copyRect(paragraph.getBoundingClientRect()),
      textFragments: Array.from(range.getClientRects(), copyRect)
    });
  } catch (error) {
    link.remove();
    paragraph.remove();
    throw error;
  } finally {
    link.onload = null;
    link.onerror = null;
    URL.revokeObjectURL(stylesheetURL);
  }
})().catch(error => console.error(error));

L’ordre essentiel est le suivant : attendre la fin du chargement de la feuille de style, établir la mise en page de l’élément cible, attendre que les polices soient prêtes, puis lire la géométrie finale. La première lecture de la géométrie établit la mise en page actuelle ; sa valeur est volontairement écartée. Les lectures finales décrivent le résultat obtenu une fois les polices prêtes. Le délai de dix secondes est une limite opérationnelle donnée à titre d’exemple : son expiration signale une tentative inachevée et ne produit aucune mesure.

Lors de l’adaptation de l’exemple, examinez fontSize et lineHeight en même temps que les blocs. getComputedStyle() renvoie les valeurs résolues des propriétés une fois les feuilles de style actives appliquées ; cette méthode est utile pour détecter un décalage de sélecteur ou de cascade. Sa valeur fontFamily décrit la liste de familles CSS ; elle ne prouve pas quelle police a fourni chaque glyphe. Window : méthode getComputedStyle().

Quelle mesure du DOM répond à votre question ?

Utilisez element.getBoundingClientRect() si vous avez besoin de connaître l’espace extérieur occupé par une carte ou un paragraphe. Son rectangle inclut le remplissage et les bordures, et sa position est relative à la fenêtre d’affichage. Dans l’exemple, la largeur déclarée de la boîte de bordure est de 280px ; la largeur de contenu disponible pour le texte est de 254px, calculée ainsi : 280 − 24 − 2. Ce calcul décrit le CSS fourni, pas un résultat observé dans le navigateur. Element : méthode getBoundingClientRect().

Utilisez un Range portant sur le texte si vous avez besoin de connaître ses fragments de mise en page. Range.getClientRects() renvoie les rectangles de la plage, et non seulement celui du paragraphe qui l’englobe. Conservez la collection : réduire un texte renvoyé à la ligne à une seule largeur fait perdre des informations utiles sur les zones qu’il occupe. Dans une implémentation générale, ne nommez pas la longueur du tableau lineCount ; un contenu en ligne complexe peut produire plusieurs rectangles. Range : méthode getClientRects().

Pendant la mesure, conservez le segment de texte d’origine intact. Ajouter une enveloppe autour de chaque caractère peut introduire des frontières de mise en forme qui affectent la composition typographique. Le projet W3C CSS Text décrit les conditions dans lesquelles les frontières entre éléments en ligne préservent ou interrompent la composition. Mesurer le DOM existant avec une plage évite d’introduire ces enveloppes. Module CSS Text niveau 3 : composition au fil des frontières entre éléments.

Canvas a un autre rôle. measureText() renvoie les métriques du texte canvas pour une chaîne donnée. Pour cette tâche, qui consiste à trouver les retours à la ligne réels du texte dans une carte du DOM, lisez le DOM une fois qu’il est prêt. Considérez la mesure sur canvas comme un calcul distinct, doté de ses propres paramètres de texte. CanvasRenderingContext2D : méthode measureText().

Que faire si les mesures changent encore ?

Suivez cette séquence de diagnostic avant d’ajouter un autre délai :

  1. Vérifiez que la fin du chargement de la feuille de style attendue concerne bien celle qui applique les styles à l’élément cible. Pour les feuilles ajoutées dynamiquement, conservez leurs promesses de chargement avec le code d’insertion.
  2. Examinez la typographie résolue et la largeur de l’élément cible. Une feuille de style chargée peut quand même perdre dans la cascade.
  3. Vérifiez que le texte cible et son conteneur prévu sont présents avant d’attendre que les polices soient prêtes.
  4. Déterminez si une police de remplacement est acceptable. Si une police déclarée précise est requise, chargez-la explicitement et signalez tout échec.
  5. Vérifiez si une autre mise à jour de l’application a modifié le texte, les classes ou le conteneur après votre attente. Recommencez la mesure pour ce nouvel état.

Un rappel d’image ne constitue pas une barrière au chargement des ressources. requestAnimationFrame() programme un rappel avant un nouveau rendu, et les navigateurs le suspendent souvent dans les onglets en arrière-plan. Utilisez-le lorsque la programmation des images sert votre application, mais ne remplacez pas les promesses des feuilles de style ou des polices par un ou deux rappels d’image. Window : méthode requestAnimationFrame().

Pour une mise en page redimensionnable, observez le conteneur concerné avec ResizeObserver et programmez de nouvelles mesures lorsque ses dimensions changent. L’observateur signale les changements de taille de la boîte de contenu ou de la boîte de bordure ; il ne détecte pas tous les changements de texte. Déclenchez aussi une mesure à partir des opérations de l’application qui remplacent le texte ou modifient la typographie. ResizeObserver.

Si vous utilisez déjà Playwright, effectuez l’attente de disponibilité et la lecture de la géométrie côté navigateur à l’intérieur d’un rappel asynchrone page.evaluate(). Playwright attend la promesse renvoyée et transmet le résultat à l’environnement appelant. Renvoyez des valeurs numériques simples pour les champs des rectangles, comme dans l’exemple, et transmettez explicitement au rappel toute valeur provenant de l’environnement appelant. Évaluer du JavaScript.

Une mesure utile correspond à un texte, à un état de feuille de style, à un résultat de chargement des polices et à une taille de conteneur donnés. Établissez ces paramètres, attendez les chargements pertinents et mesurez le DOM intact. Répétez l’opération lorsque ces paramètres changent afin que la géométrie enregistrée continue de répondre à la question de mise en page que vous vous posez réellement.

Sources