El ciclo de carga de una fuente web
Por qué el archivo de fuente se pide después del layout y no antes, qué son los periodos de bloqueo, intercambio y fallo, y qué ve el usuario en cada rama.
Una fuente web es el único subrecurso de una página cuya petición depende del layout: el navegador no sabe que la necesita hasta que ha construido el CSSOM, ha resuelto estilos y ha encontrado un elemento real que la usa. Esa dependencia explica por qué las fuentes llegan tarde, por qué existe preload, y por qué todas las técnicas de optimización tipográfica son variaciones de la misma idea: romper esa cadena.
- Describir la cadena de descubrimiento desde el HTML hasta la petición de la fuente.
- Nombrar los tres periodos del ciclo y qué ocurre al final de cada uno.
- Distinguir FOIT de FOUT y saber cuál produce cada rama.
- Medir el ciclo en las herramientas del navegador.
La cadena de descubrimiento
flowchart TB A[El navegador analiza el HTML y encuentra la hoja] --> B[La hoja bloquea el render hasta construir el CSSOM] B --> C[Se calculan estilos y un elemento usa la familia] C --> D[Solo entonces se pide el archivo de la fuente] D --> E[Periodo de bloqueo] E --> F[La fuente llega a tiempo] E --> G[La fuente no llega a tiempo] F --> H[Se pinta con la fuente web y no hay salto] G --> I[Se pinta con la fuente de respaldo] I --> J[Periodo de intercambio] J --> K[Llega y se cambia la fuente con reflujo] J --> L[Periodo de fallo y se queda la de respaldo] style A fill:#89b4fa,color:#11111b style B fill:#89b4fa,color:#11111b style C fill:#89b4fa,color:#11111b style D fill:#89b4fa,color:#11111b style E fill:#f9e2af,color:#11111b style F fill:#a6e3a1,color:#11111b style G fill:#f38ba8,color:#11111b style H fill:#a6e3a1,color:#11111b style I fill:#f9e2af,color:#11111b style J fill:#f9e2af,color:#11111b style K fill:#f9e2af,color:#11111b style L fill:#f38ba8,color:#11111b
El paso clave es el tercero. Una regla @font-face no descarga nada por sí sola: es una declaración de disponibilidad, no una instrucción de carga. El archivo se pide cuando el motor empareja un elemento del árbol de render con esa familia. De ahí salen consecuencias que sorprenden:
- Una familia declarada y no usada no cuesta ni un byte. Puedes tener veinte
@font-faceen la hoja sin coste alguno. - Un texto dentro de un elemento con
display: noneno está en el árbol de render, así que no dispara la petición. Si ese texto se muestra después, la fuente empieza a descargarse en ese momento. - Un
contentde::beforesí la dispara, porque genera una caja con texto. - La familia que aparece en un
font-familypero cuyo texto no contiene ningún carácter de suunicode-rangetampoco se pide.
Y la consecuencia grande: la petición ocurre después de descargar y analizar el CSS, que a su vez bloquea el render. En una conexión lenta eso son fácilmente 500 milisegundos antes de que la fuente ni siquiera se solicite.
Los tres periodos
Desde que se lanza la petición, la especificación de CSS Fonts define una línea temporal con tres tramos, y font-display es el descriptor que fija su duración.
Periodo de bloqueo. El texto se dispone con métricas de una fuente de respaldo pero no se pinta: hay hueco reservado y nada visible. Si la fuente llega dentro de este periodo, se pinta con ella y no hay ningún salto perceptible.
Periodo de intercambio. El texto se pinta con la fuente de respaldo. Si la fuente llega dentro de este periodo, se cambia y el texto se vuelve a componer.
Periodo de fallo. Si la fuente llega después, el navegador la considera fallida para esta carga: se queda la de respaldo. El archivo, eso sí, entra en caché y estará disponible en la siguiente navegación.
Las duraciones no están fijadas al milisegundo en la especificación, que habla de periodos corto —del orden de 100 milisegundos— y largo —del orden de 3 segundos—. En la práctica los motores usan esos valores, y lo importante no es el número exacto sino qué combinación de tramos elige cada valor de font-display.
FOIT y FOUT
Los dos acrónimos nombran las dos formas de que se note la carga.
FOIT, texto invisible instantáneo, es lo que produce un periodo de bloqueo largo: durante hasta tres segundos el usuario ve una página maquetada con huecos donde debería haber texto. El contenido está ahí, ocupa su sitio, y no se puede leer. Es el peor resultado posible para una página de texto y era el comportamiento por defecto de todos los navegadores hasta hace no tanto.
FOUT, texto sin estilar, es lo que produce un periodo de bloqueo nulo: el texto se lee inmediatamente con la fuente de respaldo y cambia de aspecto cuando llega la web. El coste es el reflujo: al cambiar de fuente cambian los anchos de los glifos, las líneas se recomponen, y todo lo que hay debajo se mueve.
La elección entre los dos parece una cuestión de gusto y no lo es: FOIT retrasa la lectura y FOUT la interrumpe, y en casi cualquier medición de experiencia real retrasar la lectura sale peor. Además, el reflujo del FOUT se puede reducir casi a cero ajustando las métricas de la fuente de respaldo, mientras que no hay forma de reducir el coste del FOIT salvo acortándolo.
Medirlo
Tres herramientas, en orden de utilidad.
El panel de red, filtrado por fuentes. Lo que buscas es el instante en que empieza la petición, no cuánto tarda. Si la fuente empieza a pedirse a los 800 milisegundos, tienes un problema de descubrimiento y ninguna optimización de tamaño lo va a arreglar.
El panel de rendimiento con la línea de tiempo. Aquí se ve el momento exacto del intercambio, porque produce un recálculo de estilo y un layout que aparecen marcados. Si ese layout provoca desplazamiento de contenido, la métrica de estabilidad visual lo recoge.
La API de fuentes desde la consola. Da la respuesta exacta sin interpretación:
document.fonts.ready.then(() => {
console.log('todas las fuentes usadas estan listas');
});
// estado de una familia concreta
document.fonts.check('1rem "Texto"'); // false hasta que carga
[...document.fonts].map(f => [f.family, f.weight, f.status]);
document.fonts.check() acepta una cadena con la sintaxis de la propiedad font abreviada, y devuelve si esa combinación concreta está disponible ya. Es la forma correcta de detectar el estado, mucho mejor que medir anchos de un elemento de prueba.
Un preescáner de HTML puede encontrar las imágenes, los scripts y las hojas de estilo leyendo el marcado, sin haber construido nada. Con las fuentes eso es imposible por una razón de fondo: saber qué fuente hace falta exige resolver la cascada, y resolver la cascada exige tener el CSS entero. No hay atajo posible dentro del modelo de CSS, porque la familia efectiva de un elemento depende de reglas que pueden estar en el último byte de la última hoja. Toda la ingeniería de rendimiento tipográfico que existe —preload, las Early Hints, los subconjuntos por unicode-range, las métricas de respaldo ajustadas, la transferencia incremental de fuentes— consiste en atacar esa única dependencia desde distintos ángulos: adelantar la petición, hacerla más pequeña, o hacer que no importe si llega tarde. Cuando evalúes una técnica nueva de carga de fuentes, colócala en ese mapa antes que nada; si no ataca ninguno de los tres frentes, no está haciendo nada por ti.