Herramientas de framework para resguardos de fuentes

Janicklas Ralph James
Janicklas Ralph James

Los sitios que cargan fuentes con font-display: swap a menudo sufren un cambio de diseño (CLS) cuando se carga la fuente web y se cambia por la de resguardo.

Puedes evitar CLS ajustando las dimensiones de la fuente de resguardo para que coincidan con las de la fuente principal. Las propiedades como size-adjust, ascent-override, descent-override y line-gap-override en la regla @font-face pueden ayudar a anular las métricas de una fuente de resguardo, lo que permite a los desarrolladores tener más control sobre cómo se muestran las fuentes. Puedes obtener más información sobre los resguardos de fuentes y las propiedades de anulación en esta entrada. También puedes ver una implementación funcional de esta técnica en esta demostración.

En este artículo, se explora cómo se implementan los ajustes de tamaño de fuente en los frameworks Next.js y Nuxt.js para generar el CSS de fuentes de resguardo y reducir el CLS. También demuestra cómo puedes generar fuentes de resguardo usando herramientas transversales como Fontaine y Capsize.

Información general

Por lo general, se usa font-display: swap para evitar el FOIT (destello de texto invisible) y mostrar el contenido más rápido en la pantalla. El valor de swap le indica al navegador que el texto que usa la fuente debe mostrarse inmediatamente con una fuente de sistema y que debe reemplazar la fuente de sistema solo cuando la fuente personalizada esté lista.

El mayor problema de swap es el efecto molesto, en el que la diferencia en el tamaño de los caracteres de las dos fuentes hace que el contenido de la pantalla cambie. Esto genera puntuaciones bajas de CLS, especialmente para sitios web con mucho texto.

En las siguientes imágenes, se muestra un ejemplo del problema. La primera imagen usa font-display: swap sin intentar ajustar el tamaño de la fuente de resguardo. En la segunda, se muestra cómo el ajuste del tamaño con la regla @font-face de CSS mejora la experiencia de carga.

Sin ajustar el tamaño de la fuente

body {
  font-family: Inter, serif;
}
Texto que cambia repentinamente de fuente y tamaño, lo que causa un efecto molesto

Después de ajustar el tamaño de la fuente

body {
  font-family: Inter, fallback-inter, serif;
  }

@font-face {
  font-family: "fallback-inter";
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0.00%;
  size-adjust: 107.40%;
  src: local("Arial");
}
Texto que genera una transición fluida a una fuente diferente

Ajustar el tamaño de la fuente de resguardo puede ser una estrategia eficaz para evitar que cambie el diseño de carga de la fuente, pero implementar la lógica desde cero puede ser complicado, como se describe en esta publicación sobre resguardos de fuentes. Por suerte, ya hay varias opciones de herramientas disponibles para facilitar esto mientras se desarrollan apps.

Cómo optimizar el resguardo de fuentes con Next.js

Next.js proporciona una forma integrada de habilitar la optimización de fuentes de resguardo. Esta función está habilitada de forma predeterminada cuando cargas fuentes con el componente @next/font.

El componente @next/font se introdujo en la versión 13 de Next.js. El componente proporciona una API para importar fuentes personalizadas o Google Fonts a tus páginas, y también incluye hosting automático integrado de archivos de fuentes.

Cuando se usan, las métricas de fuentes de resguardo se calculan automáticamente y se insertan en el archivo CSS.

Por ejemplo, si utilizas una fuente Roboto, por lo general, la definirías en CSS de la siguiente manera:

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

body {
  font-family: Roboto;
}

Para migrar a la siguiente fuente o a la siguiente, sigue estos pasos:

  1. Mueve la declaración de la fuente Roboto a tu JavaScript. Para ello, importa la función "Roboto" desde "next/font". El valor que se muestra de la función será un nombre de clase que puedes aprovechar en la plantilla de tu componente. Recuerda agregar display: swap al objeto de configuración para habilitar la función.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. En tu componente, usa el nombre de clase generado: javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }.

La opción de configuración adjustFontFallback:

Para @next/font/google: Es un valor booleano que establece si se debe usar una fuente de resguardo automática para reducir el Cambio de diseño acumulado. El valor predeterminado es verdadero. Next.js establece automáticamente la fuente alternativa en Arial o Times New Roman, según el tipo de fuente (serif y sans-serif, respectivamente).

Para @next/font/local: Es una cadena o un valor booleano falso que establece si se debe usar una fuente de resguardo automática para reducir el Cambio de diseño acumulado. Los valores posibles son Arial, Times New Roman o false. El valor predeterminado es Arial. Si deseas usar una fuente Serif, considera establecer este valor en Times New Roman.

Otra opción para las fuentes de Google

Si no puedes usar el componente next/font, otro enfoque para usar esta función con Google Fonts es a través de la marca optimizeFonts. Next.js ya tiene la función optimizeFonts habilitada de forma predeterminada. Esta función alinea el código CSS de la fuente de Google en la respuesta HTML. Además, puedes habilitar la función de ajuste de resguardo de fuentes configurando la marca experimental.adjustFontFallbacksWithSizeAdjust en tu next.config.js, como se muestra en el siguiente fragmento:

// In next.config.js
module.exports = {
 experimental: {
   adjustFontFallbacksWithSizeAdjust: true,
 },
}

Nota: No hay planes de admitir esta función con el directorio app recién introducido. A largo plazo, es ideal usar next/font.

Cómo ajustar los resguardos de fuente con Nuxt

@nuxtjs/fontaine es un módulo para el framework de Nuxt.js que calcula automáticamente los valores de la métrica de fuente de resguardo y genera el CSS @font-face de resguardo.

Para habilitar el módulo, agrega @nuxtjs/fontaine a la configuración de tus módulos:

import { defineNuxtConfig } from 'nuxt'

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
})

Si usas Google Fonts o no cuentas con una declaración @font-face para una fuente, puedes declararla como opciones adicionales.

En la mayoría de los casos, el módulo puede leer las reglas @font-face de tu CSS e inferir automáticamente los detalles, como la familia de fuentes, la familia de fuentes de resguardo y el tipo de visualización.

Si la fuente está definida en un lugar que el módulo no puede detectar, puedes pasar la información de las métricas como se muestra en el siguiente fragmento de código.

export default defineNuxtConfig({
  modules: ['@nuxtjs/fontaine'],
  fontMetrics: {
  fonts: ['Inter', { family: 'Some Custom Font', src: '/path/to/custom/font.woff2' }],
},
})

El módulo escanea automáticamente tu CSS para leer las declaraciones @font-face y genera las reglas @font-face de resguardo.

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}
/* This will be generated. */
@font-face {
  font-family: 'Roboto override';
  src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'),
    local('Arial'), local('Noto Sans');
  ascent-override: 92.7734375%;
  descent-override: 24.4140625%;
  line-gap-override: 0%;
}

Ahora puedes usar Roboto override como la fuente de resguardo en tu CSS, como se muestra en el siguiente ejemplo.

:root {
  font-family: 'Roboto';
  /* This becomes */
  font-family: 'Roboto', 'Roboto override';
}

Cómo generar el CSS por tu cuenta

Las bibliotecas independientes también pueden ayudarte a generar el CSS para ajustes de tamaño de fuente de resguardo.

Cómo usar la biblioteca de Fontaine

Si no utilizas Nuxt o Next.js, puedes usar Fontaine. Fontaine es la biblioteca subyacente que impulsa @nuxtjs/fontaine. Puedes usar esta biblioteca en tu proyecto para insertar automáticamente CSS de fuentes de resguardo con complementos de Vite o Webpack.

Imagina que tienes una fuente Roboto definida en el archivo CSS:

@font-face {
  font-family: 'Roboto';
  font-display: swap;
  src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff');
  font-weight: 700;
}

Fontaine proporciona transformadores Vite y Webpack para conectarse fácilmente a la cadena de compilación y habilitar el complemento como se muestra en el siguiente código JavaScript.

import { FontaineTransform } from 'fontaine'

const options = {
  fallbacks: ['BlinkMacSystemFont', 'Segoe UI', 'Helvetica Neue', 'Arial', 'Noto Sans'],
  // You may need to resolve assets like `/fonts/Roboto.woff2` to a particular directory
  resolvePath: (id) => 'file:///path/to/public/dir' + id,
  // overrideName: (originalName) => `${name} override`
  // sourcemap: false
}

Si usas Vite, agrega el complemento de la siguiente manera: javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Si usas Webpack, habilítalo de la siguiente manera:

// Webpack
export default {
  plugins: [FontaineTransform.webpack(options)]
}

El módulo analizará automáticamente tus archivos para modificar las reglas @font-face: css @font-face { font-family: 'Roboto'; font-display: swap; src: url('/fonts/Roboto.woff2') format('woff2'), url('/fonts/Roboto.woff') format('woff'); font-weight: 700; } /* This will be generated. */ @font-face { font-family: 'Roboto override'; src: local('BlinkMacSystemFont'), local('Segoe UI'), local('Roboto'), local('Helvetica Neue'), local('Arial'), local('Noto Sans'); ascent-override: 92.7734375%; descent-override: 24.4140625%; line-gap-override: 0%; }

Ahora puedes usar Roboto override como fuente alternativa en CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Cómo usar la biblioteca Capsize

Si no usas Next.js, Nuxt, Webpack o Vite, otra opción es usar la biblioteca de Capsize para generar el CSS de resguardo.

Nueva API de createFontStack

La API forma parte del paquete @capsize/core llamado createFontStack, que acepta un array de métricas de fuentes en el mismo orden en que especificarías tu pila de fuentes (la propiedad font-family).

Puedes consultar la documentación sobre cómo usar Capsize aquí.

Ejemplo

Considera el siguiente ejemplo: la fuente web deseada es Lobster, recurriendo a Helvetica Neue y luego a Arial. En el CSS, font-family: Lobster, 'Helvetica Neue', Arial.

  1. Importa createFontStack desde el paquete principal:

    import { createFontStack } from '@capsizecss/core';
    
  2. Importa las métricas de fuente para cada una de las fuentes deseadas (consulta Métricas de fuentes más arriba): javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. Crea tu pila de fuentes y pasa las métricas como un array, con el mismo orden que lo harías a través de la propiedad de CSS font-family. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Esto muestra lo siguiente:

{
  fontFamily: Lobster, 'Lobster Fallback: Helvetica Neue', 'Lobster Fallback: Arial',
  fontFaces: [
    {
      '@font-face' {
      'font-family': '"Lobster Fallback: Helvetica Neue"';
      src: local('Helvetica Neue');
      'ascent-override': '115.1741%';
      'descent-override': '28.7935%';
      'size-adjust': '86.8251%';
      }
     '@font-face' {
       'font-family': '"Lobster Fallback: Arial"';
       src: local('Arial');
       'ascent-override': 113.5679%;
       'descent-override': 28.392%;
       'size-adjust': 88.053%;
     }
   }
 ]
}

Debes agregar el código fontFamily y fontFaces a tu CSS. En el siguiente código, se muestra cómo implementarlo en una hoja de estilo CSS o dentro de un bloque <style>.

<style type="text/css">
  .heading {
    font-family: 
  }

  
</style>

Esto producirá el siguiente CSS:

.heading {
  font-family: Lobster, 'Lobster Fallback: Helvetica Neue',
    'Lobster Fallback: Arial';
}

@font-face {
  font-family: 'Lobster Fallback: Helvetica Neue';
  src: local('Helvetica Neue');
  ascent-override: 115.1741%;
  descent-override: 28.7935%;
  size-adjust: 86.8251%;
}
@font-face {
  font-family: 'Lobster Fallback: Arial';
  src: local('Arial');
  ascent-override: 113.5679%;
  descent-override: 28.392%;
  size-adjust: 88.053%;
}

También puedes usar el paquete @capsize/metrics para calcular los valores de anulación y aplicarlos al CSS tú mismo.

const fontMetrics = require(`@capsizecss/metrics/inter`);
const fallbackFontMetrics = require(`@capsizecss/metrics/arial`);
const mainFontAvgWidth = fontMetrics.xAvgWidth / fontMetrics.unitsPerEm;
const fallbackFontAvgWidth = fallbackFontMetrics.xAvgWidth / fallbackFontMetrics.unitsPerEm;
let sizeAdjust = mainFontAvgWidth / fallbackFontAvgWidth;
let ascent = fontMetrics.ascent / (unitsPerEm * fontMetrics.sizeAdjust));
let descent = fontMetrics.descent / (unitsPerEm * fontMetrics.sizeAdjust));
let lineGap = fontMetrics.lineGap / (unitsPerEm * fontMetrics.sizeAdjust));

Agradecimientos

Hero image de Alexander Andrews en Unsplash.