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.

Você pode impedir a CLS ajustando as dimensões da fonte substituta para corresponder à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 substituta, permitindo que os desenvolvedores tenham mais controle sobre como as fontes são exibidas. Leia mais sobre substitutos de fontes e as propriedades de substituição nesta postagem. Você também pode conferir uma implementação funcional dessa técnica nesta demonstração.

Este artigo explora como os ajustes de tamanho de fonte são implementados nas estruturas Next.js e Nuxt.js para gerar o CSS de fonte substituta 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 costuma ser usado para evitar o flash de texto invisível (FOIT, na sigla em inglês) e mostrar 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 com uma fonte do sistema e que ela precisa ser substituída apenas quando a fonte personalizada estiver pronta.

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

As imagens abaixo mostram um exemplo do problema. A primeira imagem usa font-display: swap sem tentativa de ajustar o tamanho da fonte substituta. A segunda 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 substituta pode ser uma estratégia eficaz para evitar a mudança de layout do carregamento de fontes, mas implementar a lógica do zero pode ser complicado, conforme descrito nesta postagem sobre fontes substitutas. Felizmente, várias opções de ferramentas já estão disponíveis para facilitar isso durante o desenvolvimento de aplicativos.

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 quando você carrega fontes usando o componente @next/font.

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

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

Por exemplo, se estiver usando uma fonte Roboto, você 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ó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 aproveitar no modelo de 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 componente, use o nome de 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 Cumulative Layout Shift. O padrão é "true". 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 "false" de string ou booleano que define se uma fonte substituta automática precisa ser usada para reduzir a Cumulative Layout Shift. 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 o uso do componente next/font não for uma opção, outra abordagem para usar esse recurso com o Google Fonts é pela flag optimizeFonts. O Next.js já tem o recurso OptimizeFonts ativado por padrão. Esse recurso alinha o CSS do Google Fonts na resposta HTML. Além disso, é possível ativar o recurso de ajuste de substitutos de fonte 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-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 o Google Fonts ou não tem uma declaração @font-face para uma fonte, pode declará-la como opções adicionais.

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 substitutas 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 seu 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 a fonte substituta no seu 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 independentes também podem ajudar você a gerar o CSS para ajustes no tamanho da fonte substituta.

Como usar a biblioteca Fontaine

Se você não usa Nuxt ou Next.js, pode usar o Fontaine. Fontaine é a biblioteca subjacente 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 transformadores Vite e Webpack para se conectar facilmente à cadeia de build e ativar o plug-in, como 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 seus arquivos automaticamente 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 sua 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 Next.js, Nuxt, Webpack ou 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 seguinte exemplo: a fonte da Web desejada é Lobster, voltando para Helvetica Neue e depois Arial. Em 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 fonte desejada (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ódigofontFamily e sourceFaces ao seu 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 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 por conta própria.

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.