Per misurare in modo affidabile il testo nel DOM, attendi il foglio di stile pertinente, inserisci il testo nel layout previsto e poi attendi document.fonts.ready prima di leggere la geometria. Questa guida illustra la sequenza in un browser desktop su Mac e spiega quando ripeterla.

Qui si tratta di misurare un’etichetta sintetica o un paragrafo disposto su più righe in una pagina del browser, per esempio per decidere quanto spazio serve a una scheda. Il browser deve occuparsi dell’impaginazione del testo; il tuo codice deve accertarsi che tutto sia pronto e leggere i riquadri risultanti.

Quale segnale di prontezza dovresti attendere?

Esistono traguardi distinti per la costruzione del documento, il caricamento dei fogli di stile e l’impaginazione dei font. Scegli il traguardo corrispondente al lavoro che controlli, quindi misura quando i contenuti e i relativi stili sono pronti.

Segnale Cosa garantisce Cosa richiede ancora attenzione
DOMContentLoaded L’analisi del documento e l’esecuzione degli script differiti sono completate Non attende direttamente i fogli di stile
L’evento load di un foglio di stile Quel foglio di stile e i contenuti importati sono stati caricati e analizzati Il caricamento dei font e il layout risultante dell’elemento da misurare
L’evento load della finestra Le risorse iniziali della pagina, compresi i fogli di stile, sono state caricate, escluse quelle differite Le modifiche successive dell’applicazione e la prontezza dei font
document.fonts.ready Il caricamento e l’impaginazione che coinvolgono i font usati dal documento sono completati Le successive modifiche ai contenuti, agli stili o alle dimensioni

MDN documenta questa particolarità relativa ai fogli di stile: DOMContentLoaded di per sé non li attende, anche se gli script differiti li attendono e possono quindi ritardare indirettamente l’evento. È utile per trovare gli elementi, ma non basta come condizione per eseguire una misurazione. Documento: evento DOMContentLoaded.

Per un foglio di stile che inserisci tu, associa i gestori load e error prima di inserire il link. MDN colloca l’evento load del foglio di stile dopo il caricamento e l’analisi, subito prima che gli stili inizino ad applicarsi. Anche l’elaborazione dei fogli di stile secondo WHATWG distingue il caricamento completato con successo da un evento error. Eventi di caricamento dei fogli di stile, Standard HTML: link ai fogli di stile.

Per il caricamento iniziale delle risorse di una pagina, l’evento load della finestra è un punto di controllo più ampio. Uno script che si avvia dopo deve verificare document.readyState prima di sottoscriversi, perché un evento già scattato non attiverà un gestore associato in seguito. Il codice dell’applicazione che inserisce un foglio di stile successivamente deve comunque tenere traccia esplicita di quell’inserimento. Window: evento load, Verificare lo stato di prontezza del documento.

Cosa garantisce davvero la prontezza dei font?

La specifica W3C CSS Font Loading spiega che ready viene risolta dopo il completamento delle operazioni di layout pertinenti e quando per esse non sono necessari altri caricamenti di font. Ciò comprende i casi in cui il ricorso a un font di fallback richiede caricamenti aggiuntivi. La stessa promessa viene risolta una sola volta e modifiche successive possono causare altri caricamenti di font. Leggi document.fonts.ready dopo le modifiche pertinenti a ciascuna misurazione, invece di affidarti per sempre a un’unica promessa iniziale. CSS Font Loading Module Level 3: attributo ready.

La prontezza riguarda i font usati dal documento. Non significa che tutti i font dichiarati siano stati scaricati. MDN osserva in particolare che font-display: optional può far sì che l’insieme dei font usati differisca da quello dichiarato. Una misurazione completata può quindi descrivere correttamente il testo con il font di fallback. Documento: proprietà fonts.

Decidi quale risultato ti serve prima di scrivere il codice di attesa:

  • Misurare il risultato attualmente visualizzato: attendi la prontezza dei font e leggi la geometria del DOM, accettando le scelte del browser sui font di fallback.
  • Esigere un font dichiarato e scaricabile: carica esplicitamente il font corrispondente, gestisci gli errori, quindi attendi la prontezza dei font del documento prima di misurare.
  • Misurare contenuti che cambiano: ripeti la sequenza dopo ogni aggiornamento pertinente e scarta i risultati superati da un altro aggiornamento.

Per il secondo caso, document.fonts.load() accetta una specifica CSS del font e un testo di esempio facoltativo. Con una famiglia dichiarata sintetica chiamata SampleFace, la specifica potrebbe essere 400 18px \"SampleFace\" e il testo Panel Alpha. La promessa restituita viene rifiutata se il caricamento di un font corrispondente non riesce. L’argomento di testo seleziona i font in base ai relativi intervalli Unicode dichiarati; non verifica la copertura dei singoli glifi. FontFaceSet: metodo load().

Non usare document.fonts.check() al posto di questo requisito. MDN spiega che può restituire true anche quando il font richiesto non è disponibile. Allo stesso modo, la specifica W3C stabilisce che ready non viene mai rifiutata, quindi il completamento con esito positivo da solo non dimostra che il caricamento del font sia riuscito. FontFaceSet: metodo check(), CSS Font Loading: attributo ready.

Eseguire un esempio completo dal foglio di stile alla misurazione

Sul Mac, apri about:blank in un browser desktop aggiornato, apri gli strumenti per sviluppatori e seleziona Console. Esegui una volta il seguente programma JavaScript completo. Crea un testo di esempio neutro e il relativo foglio di stile, quindi non richiede server, librerie o file di font.

L’esempio usa intenzionalmente system-ui. Mostra la sequenza di attesa senza dipendere da una risorsa scaricabile. In una pagina che usa font web, mantieni la sequenza e lascia che siano le dichiarazioni di font effettive della pagina a fornire le risorse. L’esempio per il browser è stato esaminato, ma non è stato eseguito per questo manoscritto.

(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’ordine importante è: completamento del foglio di stile, layout dell’elemento da misurare stabilito, prontezza dei font e infine letture della geometria. La prima lettura della geometria stabilisce il layout corrente; il suo valore viene intenzionalmente scartato. Le letture finali descrivono il risultato dopo la prontezza dei font. Il limite di dieci secondi è un limite operativo illustrativo: se scade, viene segnalato un tentativo incompleto e non viene prodotta alcuna misurazione.

Quando adatti l’esempio, esamina fontSize e lineHeight insieme ai riquadri. getComputedStyle() restituisce i valori risolti delle proprietà dopo l’applicazione dei fogli di stile attivi ed è quindi utile per rilevare una discrepanza nei selettori o nella cascata. Il suo valore fontFamily descrive l’elenco delle famiglie CSS; non va considerato una prova di quale font abbia fornito ogni glifo. Window: metodo getComputedStyle().

Quale misurazione del DOM risponde alla tua domanda?

Usa element.getBoundingClientRect() quando ti serve lo spazio esterno occupato da una scheda o un paragrafo. Il suo rettangolo include padding e bordi e la sua posizione è relativa alla viewport. Nell’esempio, la larghezza dichiarata del riquadro esterno è 280px; la larghezza del contenuto disponibile per il testo è 254px, calcolata come 280 − 24 − 2. Questo calcolo descrive il CSS fornito, non un risultato osservato nel browser. Elemento: metodo getBoundingClientRect().

Usa un Range sul testo se ti servono i segmenti del suo layout. Range.getClientRects() restituisce i rettangoli dell’intervallo, anziché il solo riquadro del paragrafo che lo contiene. Conserva la raccolta: appiattire il testo disposto su più righe in un’unica larghezza fa perdere informazioni utili sulle aree occupate. In un’implementazione generale, non chiamare il numero di elementi dell’array lineCount: contenuti inline complessi possono contribuire più rettangoli. Range: metodo getClientRects().

Durante la misurazione, mantieni intatto il flusso di testo originale. Aggiungere un elemento contenitore attorno a ogni carattere può introdurre confini di formattazione che influiscono sulla composizione tipografica. La bozza CSS Text del W3C specifica le condizioni in cui i confini inline mantengono o interrompono tale composizione. Misurare il DOM esistente con un intervallo evita di introdurre questi contenitori. CSS Text Module Level 3: composizione tipografica ai confini degli elementi.

Canvas ha un ruolo diverso. measureText() restituisce le metriche del testo canvas per una stringa fornita. Per questa attività, ovvero trovare come va a capo il testo dentro una scheda DOM, leggi il DOM dopo aver verificato che sia pronto. Considera la misurazione canvas un calcolo distinto, con impostazioni di testo proprie. CanvasRenderingContext2D: metodo measureText().

Cosa fare se le misurazioni continuano a cambiare?

Prima di aggiungere un altro ritardo, segui questa sequenza diagnostica:

  1. Verifica che il completamento del foglio di stile che hai atteso riguardi quello che applica gli stili all’elemento da misurare. Per i fogli inseriti dinamicamente, mantieni le relative promesse di caricamento insieme al codice di inserimento.
  2. Esamina la tipografia risolta e la larghezza dell’elemento. Un foglio di stile caricato può comunque perdere nella cascata.
  3. Verifica che il testo e il contenitore previsto siano presenti prima di attendere la prontezza dei font.
  4. Decidi se il fallback è accettabile. Se è richiesto uno specifico font dichiarato, caricalo esplicitamente e segnala eventuali errori.
  5. Verifica se un altro aggiornamento dell’applicazione ha modificato il testo, le classi o il contenitore dopo l’attesa. Riavvia la misurazione per il nuovo stato.

Una callback di frame non è una barriera al caricamento delle risorse. requestAnimationFrame() pianifica una callback prima di un ridisegno e i browser spesso la sospendono nelle schede in background. Usala quando la pianificazione dei frame è utile all’applicazione, ma non sostituire le promesse di fogli di stile o font con una o due callback di frame. Window: metodo requestAnimationFrame().

Per un layout ridimensionabile, osserva il contenitore pertinente con ResizeObserver e pianifica nuove misurazioni quando cambiano le sue dimensioni. L’osservatore segnala le variazioni delle dimensioni del content box o del border box; non è una notifica universale delle modifiche al testo. Attiva inoltre la misurazione dalle operazioni dell’applicazione che sostituiscono il testo o modificano la tipografia. ResizeObserver.

Se usi già Playwright, esegui l’attesa della prontezza e le letture della geometria nel browser all’interno di una callback asincrona page.evaluate(). Playwright attende la promessa restituita e riporta il risultato nell’ambiente chiamante. Restituisci campi numerici semplici del rettangolo, come fa l’esempio, e passa esplicitamente alla callback i valori provenienti dal chiamante. Valutare JavaScript.

Una misurazione utile corrisponde a uno specifico testo, stato dei fogli di stile, risultato dei font e dimensione del contenitore. Stabilisci questi input, attendi le operazioni di caricamento pertinenti e misura il DOM intatto. Ripeti quando gli input cambiano, così la geometria registrata continua a rispondere alla domanda di layout che ti interessa davvero.

Fonti