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;
}
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 substituta pode ser uma estratégia eficaz para evitar a mudança de layout do carregamento de fontes, mas pode ser complicado implementar a lógica do zero, como 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:
Mova a declaração da fonte Roboto para seu JavaScript importando o arquivo "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 })
No 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 Cumulative Layout Shift. 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 "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
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 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 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 @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 é possível 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 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
.
Importe createFontStack do pacote principal:
import { createFontStack } from '@capsizecss/core';
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';`
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.