Ferramentas de framework para substitutos de fonte

Janicklas Ralph James
Janicklas Ralph James

Os sites que carregam fontes com font-display: swap geralmente sofrem uma mudança de layout (CLS, na sigla em inglês) quando a fonte da Web é carregada e trocada pela substituta.

É possível evitar o CLS ajustando as dimensões da fonte de substituição para que elas correspondam às da fonte principal. Propriedades como size-adjust, ascent-override, descent-override e line-gap-override na regra @font-face podem ajudar a substituir as métricas de uma fonte de fallback, permitindo que os desenvolvedores tenham mais controle sobre como as fontes são exibidas. Leia mais sobre substitutos de fonte e as propriedades de substituição neste post. Confira uma implementação funcional dessa técnica nesta demonstração.

Este artigo explica como os ajustes de tamanho da fonte são implementados nos frameworks Next.js e Nuxt.js para gerar o CSS de fonte de substituição e reduzir o CLS. Ele também demonstra como gerar fontes substitutas usando ferramentas de corte transversal, como Fontaine e Capsize.

Contexto

font-display: swap geralmente é usado para evitar FOIT (flash de texto invisível) e mostrar conteúdo mais rapidamente na tela. O valor de swap informa ao navegador que o texto usando a fonte precisa ser exibido imediatamente usando uma fonte do sistema e que a fonte do sistema só será substituída quando a fonte personalizada estiver pronta.

O maior problema com swap é o efeito de choque, em que a diferença nos tamanhos dos caracteres das duas fontes faz com que o conteúdo da tela se mova. Isso leva a notas baixas de CLS, especialmente para sites com muito texto.

As imagens a seguir mostram um exemplo do problema. A primeira imagem usa font-display: swap sem tentativa de ajustar o tamanho da fonte substituta. O segundo mostra como ajustar o tamanho usando a regra @font-face do CSS melhora a experiência de carregamento.

Sem ajustar o tamanho da fonte

body {
  font-family: Inter, serif;
}
Texto que muda repentinamente de fonte e tamanho, causando um efeito desagradável.

Depois de ajustar o tamanho da fonte

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 faz a transição suave para uma fonte diferente.

Ajustar o tamanho da fonte de fallback pode ser uma estratégia eficaz para evitar a mudança de layout de carregamento de fontes, mas implementar a lógica do zero pode ser complicado, como descrito nesta postagem sobre fallbacks de fontes. Felizmente, várias opções de ferramentas já estão disponíveis para facilitar o desenvolvimento de apps.

Como otimizar fontes alternativas com o Next.js

O Next.js oferece uma maneira integrada de ativar a otimização de fonte de fallback. Esse recurso é ativado por padrão quando você carrega fontes usando o componente @next/font.

O componente @next/font foi introduzido na versão 13 do Next.js. O componente oferece uma API para importar fontes do Google ou personalizadas nas suas páginas e inclui autohospedagem automática integrada de arquivos de fonte.

Quando usadas, as métricas da fonte substituta são calculadas e injetadas automaticamente no arquivo CSS.

Por exemplo, se você estiver usando uma fonte Roboto, normalmente a definiria no CSS da seguinte maneira:

@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 para próxima/fonte:

  1. Mova a declaração da fonte Roboto para o seu JavaScript importando a função "Roboto" de "next/font". O valor de retorno da função será um nome de classe que você pode usar no modelo de componente. Adicione display: swap ao objeto de configuração para ativar o recurso.

     import { Roboto } from '@next/font/google';
    
    const roboto = Roboto({
      weight: '400',
      subsets: ['latin'],
      display: 'swap' // Using display swap automatically enables the feature
    })
    
  2. No componente, use o nome da classe gerada: javascript export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en" className={roboto.className}> <body>{children}</body> </html> ); }

A opção de configuração adjustFontFallback:

Para @next/font/google: um valor booleano que define se uma fonte de substituição automática precisa ser usada para reduzir o deslocamento cumulativo de layout. O padrão é verdadeiro. O Next.js define automaticamente a fonte substituta como Arial ou Times New Roman, dependendo do tipo de fonte (serif vs. sans-serif, respectivamente).

Para @next/font/local: um valor booleano ou string falso que define se uma fonte de fallback automática precisa ser usada para reduzir o deslocamento cumulativo de layout. Os valores possíveis são Arial, Times New Roman ou false. O padrão é Arial. Se você quiser usar uma fonte Serif, considere definir esse valor como Times New Roman.

Outra opção para o Google Fonts

Se não for possível usar o componente next/font, outra abordagem para usar esse recurso com as Google Fonts é pela flag optimizeFonts. O Next.js já tem o recurso optimizeFonts ativado por padrão. Esse recurso inlinea o CSS da fonte do Google na resposta HTML. Além disso, é possível ativar o recurso de ajuste de substitutos de fontes definindo a flag experimental.adjustFontFallbacksWithSizeAdjust no next.config.js, conforme mostrado no snippet a seguir:

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

Observação: não há planos de oferecer suporte a esse recurso com o diretório app recém-lançado. A longo prazo, o ideal é usar next/font.

Como ajustar substitutos de fonte com o Nuxt

@nuxtjs/fontaine é um módulo para o framework Nuxt.js que calcula automaticamente os valores da métrica da fonte substituta e gera o CSS @font-face substituto.

Ative o módulo adicionando @nuxtjs/fontaine à configuração dos módulos:

import { defineNuxtConfig } from 'nuxt'

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

Se você usa as fontes do Google ou não tem uma declaração @font-face para uma fonte, pode declarar essas opções.

Na maioria dos casos, o módulo pode ler as regras @font-face do CSS e inferir automaticamente os detalhes, como a família de fontes, a família de fontes de fallback e o tipo de exibição.

Se a fonte estiver definida em um local não detectável pelo módulo, transmita as informações de métricas conforme mostrado no snippet de código a seguir.

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

O módulo verifica automaticamente o CSS para ler as declarações @font-face e gera as regras de fallback @font-face.

@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%;
}

Agora é possível usar Roboto override como a fonte de substituição no CSS, conforme mostrado no exemplo a seguir

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

Gerar o CSS por conta própria

As bibliotecas autônomas também podem ajudar a gerar o CSS para ajustes de tamanho de fonte de fallback.

Como usar a biblioteca Fontaine

Se você não estiver usando o Nuxt ou o Next.js, use o Fontaine. Fontaine é a biblioteca subjacente que alimenta @nuxtjs/fontaine. É possível usar essa biblioteca no seu projeto para injetar automaticamente o CSS da fonte de fallback usando plug-ins do Vite ou do Webpack.

Imagine que você tem uma fonte Roboto definida no arquivo 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;
}

O Fontaine oferece transformadores Vite e Webpack para se conectar facilmente à cadeia de build. Ative o plug-in conforme mostrado no JavaScript a seguir.

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
}

Se você estiver usando o Vite, adicione o plug-in desta forma: javascript // Vite export default { plugins: [FontaineTransform.vite(options)] }

Se estiver usando o Webpack, ative-o da seguinte maneira:

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

O módulo vai verificar automaticamente seus arquivos para modificar as regras @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%; }

Agora você pode usar Roboto override como fonte substituta no CSS. css :root { font-family: 'Roboto'; /* This becomes */ font-family: 'Roboto', 'Roboto override'; }

Como usar a biblioteca Capsize

Se você não estiver usando o Next.js, o Nuxt, o Webpack ou o Vite, outra opção é usar a biblioteca Capsize para gerar o CSS substituto.

Nova API createFontStack

A API faz parte do pacote @capsize/core chamado createFontStack, que aceita uma matriz de métricas de fonte na mesma ordem em que você especificaria a pilha de fontes (a propriedade font-family).

Consulte a documentação sobre o uso do Capsize aqui.

Exemplo

Considere o exemplo a seguir: a fonte da Web desejada é Lobster, que volta para Helvetica Neue e depois para Arial. No CSS, font-family: Lobster, 'Helvetica Neue', Arial.

  1. Importe createFontStack do pacote principal:

    import { createFontStack } from '@capsizecss/core';
    
  2. Importe as métricas de cada uma das fontes desejadas (consulte "Métricas de fonte" acima): javascript import lobster from '@capsizecss/metrics/lobster'; import helveticaNeue from '@capsizecss/metrics/helveticaNeue'; import arial from '@capsizecss/metrics/arial';`

  3. Crie sua pilha de fontes, transmitindo as métricas como uma matriz, usando a mesma ordem que você faria com a propriedade CSS da família de fontes. javascript const { fontFamily, fontFaces } = createFontStack([ lobster, helveticaNeue, arial, ]);

Isso retorna o resultado a seguir:

{
  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%;
     }
   }
 ]
}

É necessário adicionar o código fontFamily e fontFaces ao CSS. O código a seguir mostra como implementá-lo em uma folha de estilo CSS ou em um bloco <style>.

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

  
</style>

Isso vai gerar o seguinte 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%;
}

Também é possível usar o pacote @capsize/metrics para calcular os valores de substituição e aplicá-los ao CSS.

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