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 fuente 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 publicación. 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 de Next.js y Nuxt.js para generar el CSS de fuente de resguardo y reducir el CLS. También se muestra cómo puedes generar fuentes de resguardo con herramientas transversales, como Fontaine y Capsize.

Segundo plano

Por lo general, font-display: swap se usa para evitar el FOIT (flash 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 de inmediato con una fuente del sistema y que reemplace la fuente del sistema solo cuando la fuente personalizada esté lista.

El mayor problema con swap es el efecto discordante, en el que la diferencia en el tamaño de los caracteres de las dos fuentes hace que el contenido de la pantalla se desplace. 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 el segundo, se muestra cómo ajustar el 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 de repente de fuente y tamaño, lo que causa un efecto discordante.

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 realiza una transición fluida a una fuente diferente.

Ajustar el tamaño de la fuente de resguardo puede ser una estrategia eficaz para evitar el cambio de 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 los resguardos de fuentes. Afortunadamente, ya hay varias opciones de herramientas disponibles para facilitar esto durante el desarrollo de 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 de Google o fuentes personalizadas a tus páginas y, además, incluye la autoalojación automática integrada 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 usas 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 next/font, haz lo siguiente:

  1. Importa la función "Roboto" de "next/font" para mover la declaración de la fuente Roboto a tu código JavaScript. El valor que se muestra de la función será un nombre de clase que puedes aprovechar en tu plantilla de componentes. 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 acumulativo. El valor predeterminado es verdadero. Next.js establece automáticamente la fuente de resguardo en Arial o Times New Roman según el tipo de fuente (serif o 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 acumulativo. Los valores posibles son Arial, Times New Roman o false. El valor predeterminado es Arial. Si quieres 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 habilitada la función optimizeFonts 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 para admitir esta función con el directorio app que se presentó recientemente. A largo plazo, lo ideal es usar next/font.

Cómo ajustar los resguardos de fuentes con Nuxt

@nuxtjs/fontaine es un módulo para el framework de Nuxt.js que calcula automáticamente los valores de métrica de la 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 se define 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 analiza automáticamente tu CSS para leer las declaraciones @font-face y genera las reglas de @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';
}

Genera el CSS por tu cuenta

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

Cómo usar la biblioteca de Fontaine

Si no usas 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 el CSS de la fuente 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 conectarlos a la cadena de compilación fácilmente 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 fuente en el mismo orden en que especificarías tu pila de fuentes (la propiedad font-family).

Puedes consultar la documentación sobre el uso de Capsize aquí.

Ejemplo

Considera el siguiente ejemplo: la fuente web deseada es Lobster, que se reemplaza por Helvetica Neue y, luego, por Arial. En 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 fuente 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 usarías a través de la propiedad font-family de CSS. 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 de 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 por tu cuenta.

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