FlowingDev

@font-face, explicado: ensinando os navegadores a falar sua tipografia

Domine a regra CSS @font-face para entregar web fonts customizadas com eficiência usando font-display, preloading, unicode-ranges e pesos variáveis.

Testar a ferramenta: Gerador de @font-face

Em uma frase

@font-face é a regra CSS que permite ao navegador baixar e renderizar uma tipografia customizada, finalmente libertando os designers da tirania da meia dúzia de fontes "web-safe" (seguras para web) pré-instaladas em todo computador.

O problema que resolve

Se você construía sites no final dos anos 90 ou começo dos 2000, a tipografia na web era um cenário triste e desolador. Você ficava à mercê das fontes que calhavam de estar instaladas no sistema operacional do usuário. Você escrevia font-family: Arial, Helvetica, sans-serif; e rezava. Se um designer te entregasse um mockup no Photoshop com uma fonte serifada linda e exclusiva para os títulos, você tinha três opções terríveis:

  1. A Gambiarra da Imagem: Fatiar o título no Photoshop e servi-lo numa tag <img>. O SEO odiava. Leitores de tela não conseguiam ler. Mais tarde, as telas Retina transformariam tudo numa bagunça borrada.
  2. sIFR (Scalable Inman Flash Replacement): Uma gambiarra demoníaca que usava JavaScript para encontrar o texto, ocultá-lo e embutir um filminho minúsculo em Adobe Flash que renderizava exatamente o mesmo texto numa fonte customizada. Era pesado, cheio de bugs e derretia a bateria dos notebooks.
  3. Cufón: Uma biblioteca JavaScript que convertia as curvas da fonte em gráficos vetoriais e os renderizava num <canvas>. Lindo, mas você não conseguia selecionar ou copiar o texto facilmente.

O W3C chegou a propor o @font-face lá atrás, no CSS2 (1998), mas os navegadores não entravam em consenso sobre os formatos das fontes, e as foundries (fundições de tipos) morriam de medo da pirataria (se você colocasse um arquivo .ttf no servidor, qualquer um podia baixar).

Foi só na década de 2010, com a adoção em massa do Web Open Font Format (WOFF) — um formato comprimido e feito especificamente para a web — que o @font-face virou uma realidade prática. Hoje, o @font-face não serve apenas para carregar fontes; ele orquestra uma dança complexa de requisições de rede, cache, subsetting e estratégias de renderização para manter o seu site absurdamente rápido, mantendo o visual exato que o designer imaginou.

Como funciona por debaixo dos panos

Na sua essência, o @font-face é um conjunto de instruções. Ele mapeia um nome que você inventa para um arquivo que está em um servidor.

A Anatomia da Regra

Uma declaração @font-face moderna e pronta para produção se parece com isso:

@font-face {
  font-family: 'MyAwesomeFont';
  src: local('MyAwesomeFont Regular'), 
       url('/fonts/myawesomefont.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF;
}

Vamos desvendar a magia dentro desse bloco.

Formatos e o Monopólio do WOFF2

Repare no format('woff2'). Na idade das trevas, os devs tinham que fornecer uma pilha enorme e "à prova de balas" com formatos .eot (para o falecido Internet Explorer), .ttf (para Safari/Android mais velhos), .svg (para o finado iOS) e .woff.

Hoje, a web roda em WOFF2. Ele usa compressão Brotli, resultando em tamanhos de arquivo quase 30% menores que seu antecessor, o WOFF. Como o suporte global ao WOFF2 é quase universal (mais de 97%), geralmente você só precisa servir um único arquivo .woff2, talvez com um fallback em WOFF padrão caso você atenda a máquinas corporativas super antigas.

Controlando o Flash (font-display)

Quando um navegador encontra um texto estilizado com uma fonte customizada, mas o arquivo da fonte ainda não terminou de ser baixado, ele tem que fazer uma escolha. Ele exibe o texto usando uma fonte de fallback? Ou esconde o texto até a fonte customizada chegar?

Isso cria duas siglas infames: FOUT (Flash of Unstyled Text) e FOIT (Flash of Invisible Text). O descritor font-display permite que você dite esse comportamento.

font-display Comportamento Quando usar
auto Padrão do navegador (geralmente bloqueia a renderização por até 3 segundos). Nunca, se você se importa com performance.
swap Mostra a fonte de fallback imediatamente (FOUT) e depois faz a troca (swap) pela customizada quando pronta. Textos de body, artigos, elementos da UI. Os usuários podem começar a ler na hora.
block Esconde o texto (FOIT) por um tempo, então mostra o fallback e depois faz a troca. Ícones ou logos onde a fonte de fallback ficaria parecendo um jargão incompreensível.
fallback Bloqueio curto, tempo de troca curto. Se demorar demais, aborta a fonte customizada de vez naquele carregamento da página. Texto decorativo em conexões mais lentas.
optional Bloqueio absurdamente curto. Se a fonte não estiver no cache ou não carregar voando, ele desiste de vez. Fontes tipo "nice to have" (legal ter), onde a estabilidade do layout é crucial.

Subsetting com unicode-range

Um arquivo de fonte completo, contendo glifos em Latim, Cirílico, Grego e os complexos CJK (Chinês, Japonês e Coreano) pode pesar vários megabytes.

A propriedade unicode-range é uma ferramenta de otimização brilhante. Ela diz ao navegador: "Esse arquivo de fonte só cobre esses caracteres específicos." Se você definir unicode-range: U+0000-00FF (Latim Básico), o navegador vai analisar o seu HTML. Se ele não encontrar nenhum caractere latino, ele nem chega a baixar o arquivo da fonte. Isso permite que você divida fontes gigantescas em pedacinhos minúsculos separados por idioma, carregando somente o que o usuário realmente precisa.

Fontes Variáveis (Variable Fonts)

Tradicionalmente, se você quisesse os estilos Regular, Itálico, Bold (negrito) e Bold Itálico, você precisava carregar quatro arquivos .woff2 separados.

As fontes variáveis (variable fonts) embutem uma família inteira em um único arquivo, definindo "eixos de variação". Em vez de pesos estáticos como 400 ou 700, uma fonte variável pode ser renderizada com um peso de 432 ou 781.

@font-face {
  font-family: 'MyVariableFont';
  src: url('/fonts/myfont-var.woff2') format('woff2');
  font-weight: 100 900; /* Accepts any weight between 100 and 900 */
}

Embutir com Base64 vs. Preloading

Às vezes, a própria requisição de rede é o gargalo. Você tem duas estratégias avançadas:

  1. Preloading: Adicionar <link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin> no <head> do seu HTML diz ao navegador para buscar a fonte imediatamente, antes mesmo de baixar o CSS.
  2. Embutir com Base64: Você pode converter o arquivo binário da fonte em uma string de texto gigante e colar direto no url() da sua regra @font-face. Isso elimina totalmente a requisição de rede, mas infla o tamanho do arquivo CSS em uns 33%. Isso trava toda a renderização da página até que o CSS seja baixado, então é algo estritamente reservado para fontes minúsculas, hiper-otimizadas como um subset (tipo um logo customizado).

Histórias do mundo real

O Botão Invisível do E-commerce

Um e-commerce boutique decidiu usar uma fonte de ícones customizada na interface. Eles usaram uma declaração @font-face padrão, sem font-display. No Wi-Fi rápido do escritório, tudo ficava maravilhoso.

Porém, usuários acessando via mobile em redes 3G estavam abandonando o site em níveis recordes. Quando os devs simularam uma rede lenta, acharam o culpado: o navegador esperava a fonte de ícones carregar para só então renderizar o botão de "Adicionar ao Carrinho". O resultado era um Flash of Invisible Text (FOIT) de 4 segundos. Os usuários literalmente não conseguiam ver o botão para dar dinheiro pra empresa. Simplesmente adicionar o font-display: swap; para textos normais e usar o preload na fonte de ícones salvou a taxa de conversão deles.

A Tragédia Tipográfica de Vários Megabytes

Um blog famoso de tecnologia decidiu suportar internacionalização. Para garantir que sua tipografia chique ficasse sempre consistente, incluíram uma Noto Sans gigantesca que cobria todos os caracteres CJK (Chinês, Japonês e Coreano). O arquivo da fonte tinha quase 6MB.

De repente, usuários nos EUA, lendo artigos normais em inglês, começaram a reclamar do tempo de carregamento lento. Seus navegadores estavam baixando 6MB de glifos japoneses e coreanos apenas para ler um artigo sobre JavaScript. O time então implementou o unicode-range. Dividiram a fonte em um subset latino de 30KB e um subset CJK de 5.9MB. O navegador passava a pedir automaticamente apenas o arquivo de 30KB para leitores em inglês, curando na hora a queda de performance.

O Redesign Tremelique

Uma agência de design bolou um layout editorial maravilhoso usando nove pesos diferentes de uma fonte clássica sem serifa. O dev, diligentemente, exportou nove arquivos .woff2 diferentes e escreveu nove regras @font-face.

Como os navegadores baixam arquivos de forma assíncrona, a página brotava gaguejando e dando trancos. O peso 300 carregava, depois o 700, depois o 400, fazendo com que o layout redesenhasse e recalculasse a quebra dos textos umas quatro ou cinco vezes por segundo. O dev então substituiu os nove arquivos por uma única Fonte Variável (.woff2-variations). O arquivo único era 40% menor do que os nove juntos, carregava em uma só requisição e renderizava a página inteira de uma vez só.

Erros comuns e armadilhas

  • Servir formatos pré-históricos para navegadores modernos: Pare de servir fontes .ttf, .eot e .svg, a não ser que você tenha um motivo muito explícito, apoiado em dados, para dar suporte ao IE8 ou a um Android de uma década atrás. Fique no WOFF2.
  • Esquecer o crossorigin nos preloads: Se você usa o <link rel="preload"> para uma fonte, você tem que incluir o atributo crossorigin, mesmo que a fonte esteja hospedada no seu próprio domínio. Graças às regras de segurança do navegador, as fontes são requisitadas via CORS no modo anônimo. Se você não colocar o atributo, o navegador vai baixar a fonte duas vezes.
  • Fazer encoding Base64 de fontes normais (body): Embutir uma fonte de 100KB direto no seu CSS infla brutalmente o seu payload de CSS e bloqueia a renderização. Codifique apenas fontes pequenininhas (tipo um subset de 5KB para um logo).
  • Ignorar o font-display: Depender do padrão do navegador quase sempre vai punir quem tem conexões lentas. Declare explicitamente o font-display: swap para os textos do corpo da página para priorizar a leitura no lugar da estética.
  • Dar preload em todas as fontes da página: O preload é uma instrução de altíssima prioridade. Se você der preload numa fonte itálico-negrito que fica no rodapé do site, você estará roubando a banda de rede do JavaScript e CSS críticos necessários para carregar o topo da página. Dê preload apenas na fonte principal (a fonte "hero").
  • Confiar demais no local(): Usar local('Helvetica') diz para o navegador usar uma cópia local se ela existir. Porém, as convenções de nome de fonte variam de forma insana entre Mac, Windows e Linux. O que você acha que é local('Helvetica Neue Bold') pode falhar silenciosamente num sistema operacional diferente.

Por que isso deve estar no seu radar

O desenvolvimento web é um cabo de guerra eterno entre estética e performance. Você está construindo uma marca, e a tipografia é a voz dessa marca. Mas você também está construindo software, e cada kilobyte importa para um usuário usando uma conexão capenga de internet no metrô.

Escrever a regra @font-face perfeita é notoriamente chato e repetitivo. Exige um entendimento profundo das maluquices dos navegadores, MIME types, sintaxe de subsetting e formatos de compressão. Um gerador moderno não se limita a escrever a sintaxe CSS pra você; ele já embute as melhores práticas da web. Ele gera os arquivos WOFF2 automaticamente, estrutura os eixos da fonte variável, aplica os fallbacks corretos para o font-display e entrega as exatas tags Base64 ou de preload para desviar dos gargalos de rede. Ele garante que, quando sua aplicação falar, o navegador vai realmente saber como pronunciar as palavras.

Vá mais fundo

Teoria feita. Hora de pôr a mão na massa — 100% no seu navegador.

Testar a ferramenta: Gerador de @font-face