Para medir el texto del DOM de forma fiable, espera a que se cargue la hoja de estilos pertinente, coloca el texto en la disposición que tendrá y, después, espera a que se resuelva document.fonts.ready antes de leer la geometría. Esta guía muestra la secuencia en un navegador de escritorio para Mac y explica cuándo repetirla.

La tarea consiste en medir una etiqueta sintética o un párrafo con ajuste de línea en una página del navegador; por ejemplo, decidir cuánto espacio necesita una tarjeta. El navegador debe encargarse de la composición del texto; tu código debe establecer que todo esté listo y leer los cuadros resultantes.

¿A qué señal de disponibilidad debes esperar?

Hay hitos distintos para la construcción del documento, la carga de hojas de estilos y la composición tipográfica. Elige el hito que corresponda al trabajo que controlas y mide después de que el contenido y sus estilos estén en su sitio.

Señal Qué confirma Qué falta por atender
DOMContentLoaded Se completaron el análisis del documento y la ejecución de los scripts diferidos No espera directamente a que se carguen las hojas de estilos
El evento load de una hoja de estilos Se cargaron y analizaron esa hoja de estilos y su contenido importado La carga de las fuentes y la disposición resultante del elemento objetivo
El evento load de la ventana Se cargaron los recursos iniciales de la página, incluidas las hojas de estilos, salvo los recursos de carga diferida Los cambios posteriores de la aplicación y la disponibilidad de las fuentes
document.fonts.ready Se completaron la carga y la composición tipográfica que usan las fuentes del documento Los cambios posteriores de contenido, estilo o tamaño

MDN documenta un matiz relacionado con las hojas de estilos: DOMContentLoaded no espera por sí mismo a que se carguen, aunque los scripts diferidos sí las esperan y, por tanto, pueden retrasar indirectamente el evento. Es útil para buscar elementos, pero no basta como garantía para medir. Documento: evento DOMContentLoaded.

Si insertas una hoja de estilos, asigna los controladores de load y error antes de insertar el enlace. MDN indica que el evento load de la hoja de estilos se produce una vez cargada y analizada, justo antes de que empiecen a aplicarse los estilos. El procesamiento de hojas de estilos de WHATWG también distingue entre una carga correcta y un evento error. Eventos de carga de hojas de estilos, Estándar HTML: enlaces a hojas de estilos.

Para la carga inicial de recursos de una página, el evento load de la ventana es un punto de control más amplio. Si un script empieza después, debe comprobar document.readyState antes de suscribirse, porque un evento que ya se produjo no volverá a ejecutar un controlador que se añada más tarde. El código de la aplicación que inserte una hoja de estilos posteriormente debe seguir ese proceso de inserción de forma explícita. Ventana: evento load, Comprobar si el documento está listo.

¿Qué garantiza realmente que las fuentes estén listas?

La especificación CSS Font Loading del W3C explica que ready se resuelve después de que se completen las operaciones de composición pertinentes y ya no se necesiten más cargas de fuentes para ese trabajo. Esto incluye los casos en que el uso de una fuente alternativa provoca cargas adicionales. La misma promesa se resuelve una sola vez, y los cambios posteriores pueden provocar nuevas cargas. Lee document.fonts.ready después de los cambios relevantes para cada medición, en lugar de depender para siempre de una sola promesa inicial. CSS Font Loading Module Level 3: atributo ready.

La disponibilidad se refiere a las fuentes que usa el documento. No significa que se hayan descargado todas las fuentes declaradas. MDN señala específicamente que font-display: optional puede hacer que el conjunto de fuentes usadas difiera del conjunto declarado. Por tanto, una medición completada puede describir correctamente el texto con una fuente alternativa. Documento: propiedad fonts.

Decide qué resultado necesitas antes de escribir el código de espera:

  • Medir el resultado renderizado actual: espera a que las fuentes estén listas y lee la geometría del DOM, aceptando las decisiones del navegador sobre fuentes alternativas.
  • Exigir una fuente declarada y descargable: carga explícitamente la fuente correspondiente, gestiona los errores y luego espera a que las fuentes del documento estén listas antes de medir.
  • Medir contenido que cambia: repite la secuencia tras cada actualización pertinente y descarta los resultados que queden obsoletos por otra actualización.

Para el segundo caso, document.fonts.load() acepta una especificación de fuente CSS y texto de muestra opcional. Con una familia declarada sintética llamada SampleFace, la especificación podría ser 400 18px \"SampleFace\" y el texto podría ser Panel Alpha. La promesa devuelta se rechaza si no se carga una fuente coincidente. El argumento de texto selecciona fuentes mediante los rangos Unicode declarados; no verifica la cobertura de glifos individuales. FontFaceSet: método load().

No sustituyas ese requisito por document.fonts.check(). MDN explica que puede devolver true incluso cuando la fuente solicitada no está disponible. Asimismo, el W3C especifica que ready nunca se rechaza, por lo que completarse correctamente no demuestra por sí solo que la carga de fuentes haya tenido éxito. FontFaceSet: método check(), CSS Font Loading: atributo ready.

Ejecuta un ejemplo completo, desde la hoja de estilos hasta la medición

En tu Mac, abre about:blank en un navegador de escritorio actualizado, abre las herramientas de desarrollo y selecciona Console. Ejecuta una vez el siguiente programa JavaScript completo. Crea su propio texto de muestra neutro y su hoja de estilos, así que no necesita servidor, biblioteca ni archivo de fuentes.

El ejemplo usa deliberadamente system-ui. Demuestra la secuencia de espera sin depender de un recurso descargable. En una página que use fuentes web, conserva la secuencia y deja que las declaraciones de fuentes de esa página proporcionen los recursos. El ejemplo del navegador se revisó, pero no se ejecutó para este manuscrito.

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

El orden importante es completar la hoja de estilos, establecer la disposición del elemento objetivo, esperar a que las fuentes estén listas y, después, leer la geometría final. La primera lectura de geometría establece la disposición actual; su valor se descarta intencionalmente. Las lecturas finales describen el resultado una vez listas las fuentes. El plazo de diez segundos es un límite operativo ilustrativo: si se agota, se informa de un intento sin terminar y no se genera ninguna medición.

Al adaptar el ejemplo, examina fontSize y lineHeight junto con los cuadros. getComputedStyle() devuelve los valores resueltos de las propiedades una vez aplicadas las hojas de estilos activas, por lo que sirve para detectar discrepancias en el selector o la cascada. Su valor fontFamily describe la lista de familias CSS; no debe tomarse como prueba de qué fuente proporcionó cada glifo. Ventana: método getComputedStyle().

¿Qué medición del DOM responde a tu pregunta?

Usa element.getBoundingClientRect() cuando necesites saber el espacio exterior que ocupa una tarjeta o un párrafo. El rectángulo incluye el relleno y los bordes, y su posición se mide respecto de la ventana gráfica. En el ejemplo, el ancho declarado de la caja de borde es 280px; el ancho de contenido disponible para el texto es 254px, calculado como 280 − 24 − 2. Ese cálculo describe el CSS proporcionado, no un resultado observado en el navegador. Elemento: método getBoundingClientRect().

Usa un Range sobre el texto cuando necesites sus fragmentos de composición. Range.getClientRects() devuelve los rectángulos del rango, no solo el cuadro que contiene el párrafo. Conserva la colección: reducir el texto ajustado a un solo ancho hace perder información útil sobre las regiones que ocupa. En una implementación general, no llames lineCount a la longitud del arreglo; el contenido en línea complejo puede aportar varios rectángulos. Range: método getClientRects().

Mantén intacto el fragmento de texto original mientras lo mides. Añadir un contenedor alrededor de cada carácter puede introducir límites de formato que afecten a la composición tipográfica. El borrador CSS Text del W3C especifica las condiciones en las que los límites entre elementos en línea conservan o interrumpen la composición. Medir el DOM existente con un rango evita introducir esos contenedores. CSS Text Module Level 3: composición a través de los límites entre elementos.

Canvas tiene una función distinta. measureText() devuelve métricas del texto de canvas para una cadena proporcionada. Para esta tarea —determinar cómo se ajusta realmente el texto dentro de una tarjeta del DOM— lee el DOM cuando esté listo. Considera la medición de canvas como un cálculo aparte, con su propia configuración de texto. CanvasRenderingContext2D: método measureText().

¿Qué debes hacer si las mediciones siguen cambiando?

Antes de añadir otra espera, sigue estos pasos de diagnóstico:

  1. Confirma que la hoja de estilos cuya carga esperaste sea la que da estilo al elemento objetivo. Si insertas hojas dinámicamente, conserva sus promesas de carga junto con el código de inserción.
  2. Inspecciona la tipografía y el ancho resueltos del elemento objetivo. Una hoja de estilos cargada aún puede perder en la cascada.
  3. Confirma que el texto objetivo y su contenedor previsto estén presentes antes de esperar a que las fuentes estén listas.
  4. Decide si las fuentes alternativas son aceptables. Si necesitas una fuente declarada específica, cárgala explícitamente e informa de los errores.
  5. Comprueba si otra actualización de la aplicación cambió el texto, las clases o el contenedor después de la espera. Reinicia la medición para ese nuevo estado.

Una devolución de llamada de fotograma no es una barrera para la carga de recursos. requestAnimationFrame() programa una devolución de llamada antes del repintado, y los navegadores suelen pausarla en pestañas en segundo plano. Úsala cuando la programación de fotogramas sirva a tu aplicación, pero no la sustituyas por una o dos devoluciones de llamada de fotograma en lugar de las promesas de las hojas de estilos o las fuentes. Ventana: método requestAnimationFrame().

Para una disposición que pueda cambiar de tamaño, observa el contenedor pertinente con ResizeObserver y programa nuevas mediciones cuando cambien sus dimensiones. El observador informa de cambios en el tamaño de la caja de contenido o de borde; no es una notificación universal de cambios en el texto. También debes activar la medición desde las operaciones de la aplicación que sustituyen el texto o cambian la tipografía. ResizeObserver.

Si ya usas Playwright, realiza la espera de disponibilidad del navegador y las lecturas de geometría dentro de una devolución de llamada asíncrona page.evaluate(). Playwright espera a que se resuelva la promesa devuelta y devuelve el resultado al entorno que lo llamó. Devuelve los campos numéricos simples del rectángulo, como hace el ejemplo, y pasa explícitamente a la devolución de llamada los valores que se originen en quien la invoca. Evaluar JavaScript.

Una medición útil corresponde a un texto, un estado de la hoja de estilos, un resultado de fuente y un tamaño de contenedor concretos. Establece esos datos, espera a que se complete el trabajo de carga pertinente y mide el DOM intacto. Repite el proceso cuando cambien esos datos para que la geometría registrada siga respondiendo a la pregunta de disposición que realmente tienes.

Fuentes