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) quando a fonte da Web é carregada e trocada pela substituta.

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

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

Contexto

Em geral, font-display: swap é usado para evitar o flash de texto invisível (FOIT, na sigla em inglês) e para exibir o conteúdo mais rapidamente na tela. O valor de swap informa ao navegador que o texto que usa a fonte precisa ser exibido imediatamente usando uma fonte do sistema e que só a substitui quando a personalizada estiver pronta.

O maior problema com swap é o efeito chocante, em que a diferença no tamanho dos caracteres das duas fontes resulta na mudança do conteúdo da tela. Isso leva a pontuações de CLS baixas, especialmente para sites com muito texto.

As imagens a seguir mostram um exemplo do problema. A primeira imagem usa font-display: swap sem tentar ajustar o tamanho da fonte substituta. A segunda mostra como o ajuste do tamanho usando a regra CSS @font-face 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 chocante.

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 substituta pode ser uma estratégia eficaz para evitar a mudança do layout de carregamento de fonte, mas implementar a lógica do zero pode ser complicado, conforme descrito nesta postagem sobre substitutos de fonte. Felizmente, já existem várias opções de ferramentas disponíveis para facilitar esse processo no desenvolvimento de apps.

Como otimizar fontes substitutas com o Next.js.

O Next.js oferece uma maneira integrada de ativar a otimização de fontes substitutas. Esse recurso é ativado por padrão ao carregar fontes usando o componente @next/font.

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

Quando usadas, as métricas de fontes substitutas são calculadas e injetadas automaticamente no arquivo CSS.

Por exemplo, se você usar uma fonte Roboto, normalmente a definiria em 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óximo/fonte":

  1. Mova a declaração da fonte Roboto para 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 do componente. Lembre-se de adicionar 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 seu componente, use o nome da classe gerado: 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 substituta automática precisa ser usada para reduzir a Mudança de layout cumulativa. O padrão é "true". O Next.js define automaticamente a fonte substituta como Arial ou Times New Roman, dependendo do tipo de fonte (serif ou Sans Serif, respectivamente).

Para @next/font/local:é uma string ou um valor booleano falso que define se uma fonte substituta automática precisa ser usada para reduzir a Mudança de layout cumulativa. Os valores possíveis são Arial, Times New Roman ou false. O padrão é Arial. Se você quiser usar uma fonte com serifa, defina esse valor como Times New Roman.

Outra opção para o Google Fonts

Caso o uso do componente next/font não seja uma opção, outra abordagem para usar esse recurso com o Google Fonts é pela sinalização optimizeFonts. O Next.js já tem o recurso optimizeFonts ativado por padrão. Esse recurso coloca o CSS da fonte do Google inline na resposta HTML. Além disso, é possível ativar o recurso de ajuste de substitutos definindo a sinalização experimental.adjustFontFallbacksWithSizeAdjust em 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-apresentado. A longo prazo, o ideal é usar next/font.

Como ajustar fontes substitutas com o Nuxt

@nuxtjs/fontaine é um módulo para o framework Nuxt.js que calcula automaticamente os valores da métrica de 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 o Google Fonts ou não tem uma declaração @font-face para uma fonte, é possível declará-la como outras 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 substituta e o tipo de exibição.

Se a fonte estiver definida em um local não detectável pelo módulo, você poderá transmitir 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 @font-face substitutas.

@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, conforme mostrado no exemplo a seguir.

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

Como gerar o CSS por conta própria

Bibliotecas autônomas também podem ajudar a gerar o CSS para ajustes de tamanho de fonte substituta.

Como usar a biblioteca Fontaine

Se você não estiver usando Nuxt ou Next.js, poderá usar Fontaine. Fontaine é a biblioteca que alimenta @nuxtjs/fontaine. Você pode usar essa biblioteca no seu projeto para injetar automaticamente o CSS de fonte substituta usando os plug-ins Vite ou 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 fornece os transformadores Vite e Webpack para conectar com facilidade à cadeia de build e ativar 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 deste modo: 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 de @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 usa o Next.js, Nuxt, Webpack ou Vite, outra opção é utilizar a biblioteca Capsize (em inglês) 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 que você especificaria sua pilha de fontes (a propriedade font-family).

Consulte a documentação sobre como usar o Capsize aqui.

Exemplo

Considere o seguinte exemplo: a fonte da web desejada é Lobster, voltando para Helvetica Neue e, em seguida, 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 a pilha de fontes, transmitindo as métricas como uma matriz, na mesma ordem que você faria na 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 fazer a implementação em uma folha de estilo CSS ou em um bloco <style>.

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

  
</style>

Isso vai produzir 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%;
}

Você também pode 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));

Agradecimentos

Imagem principal de Alexander Andrews no Unsplash (links em inglês).