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;
}
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");
}
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:
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 })
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
.
Importe createFontStack do pacote principal:
import { createFontStack } from '@capsizecss/core';
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';`
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));