A adoção de web fonts continua a acelerar em toda a web: de acordo com o HTTP Archieve, aproximadamente 37% dos 300 mil principais sites estão usando web fonts desde o início de 2014, o que se traduz em um aumento de mais de duas vezes ao longo dos últimos doze meses. Claro, isso não deve ser tão surpreendente para a maioria de nós. Tipografia sempre foi uma parte importante de um bom design, branding e legibilidade, e web fonts oferecem muitos benefícios adicionais: o texto é selecionável, pesquisável, com zoom e amigável a uma alta DPI. O que há para não se gostar?
Ah, mas e quanto à velocidade de renderização, web fonts não vêm as custas do desempenho? Elas são um recurso crítico a mais na página, então, sim, podem afetar a velocidade de renderização de nossas páginas. Dito isso, só porque a página está usando web fonts não significa que ela vai (ou deva) se tornar mais lenta.
Há quatro alavancas principais que determinam o impacto de web fonts no desempenho da página:
- O número total de fontes e o peso das fontes utilizadas na página.
- O tamanho total dos bytes das fontes usadas na página.
- A latência da transferência do recurso da fonte.
- O momento em que o download das fontes é iniciado.
As duas primeiras alavancas estão diretamente sob o controle do designer da página. Quanto mais fontes são usadas, mais pedidos serão feitos, o que resultará em mais bytes. As melhores práticas de UX dizem que em geral é bom manter o número de fontes utilizadas no mínimo, o que também se alinha com as nossas metas de desempenho. Passo um: usar web fonts, mas auditar o uso de fontes periodicamente e tentar mantê-las enxutas.
Medir as latências de web fonts
A latência de transferência de cada arquivo fonte depende de seu tamanho, que por vezes é determinado pelo número de glifos, metadados da fonte (por exemplo, hinting para plataformas Windows) e método de compressão utilizado. Técnicas como a subdivisão de fontes, otimização específica e compressão mais eficiente (por exemplo, o Google Fonts recentemente adotou o Zopfli para recursos WOFF) são a chave para otimizar o tamanho de transferência. Além disso, uma vez que estamos falando de latência, a partir de onde a fonte é servida também faz a diferença – ou seja, um CDN e, idealmente, o cache do usuário!
Dito isso, em vez de falar em abstrato, quanto tempo o visitante leva para baixar o recurso da web font em seu site? A melhor maneira de responder a essa pergunta é instrumentalizar o seu site por meio da API de Resource Timing, que nos permite obter tempo de transferência do DNS, TCP, e os dados do tempo de transferência para cada fonte – como bônus, o Google Fonts recentemente habilitou suporte para Resource Timing! Aqui está um trecho de exemplo para relatar latências de fonte ao Google Analytics:
// check if visitor's browser supports Resource Timing if (typeof window.performance == 'object') { if (typeof window.performance.getEntriesByName == 'function') { function logData(name, r) { var dns = Math.round(r.domainLookupEnd - r.domainLookupStart), tcp = Math.round(r.connectEnd - r.connectStart), total = Math.round(r.responseEnd - r.startTime); _gaq.push( ['_trackTiming', name, 'dns', dns], ['_trackTiming', name, 'tcp', tcp], ['_trackTiming', name, 'total', total] ); } var _gaq = _gaq || []; var resources = window.performance.getEntriesByType("resource"); for (var i in resources) { if (resources[i].name.indexOf("themes.googleusercontent.com") != -1) { logData("webfont-font", resources[i]) } if (resources[i].name.indexOf("fonts.googleapis.com") != -1) { logData("webfont-css", resources[i]) } } } }
O exemplo acima captura as principais métricas de latência, tanto para o arquivo CSS UA-otimizado quanto para os arquivos de fontes especificadas nesse arquivo: o CSS fica em fonts.googleapis.com e é armazenado em cache por 24 horas, e os arquivos de fontes ficam em themes.googleusercontent.com e demoram para expirar. Com isso no lugar, vamos dar uma olhada no tempo total dos dados (responseEnd – startTime) do Google Analytics para o meu site:
Por razões de privacidade, a API de Resource Timing intencionalmente não fornece o indicador “obtido a partir do cache”, mas ainda assim pode utilizar um limite razoável de tempo – digamos, 20ms – para obter uma aproximação. Por que 20ms? Buscar um arquivo de a partir de discos metálicos girando, ou mesmo em memória flash, não sai sem custos. O tempo de busca em cache irá variar com base no hardware, mas, para os nossos propósitos, adotaremos um limite relativamente agressivo de 20ms.
Com isso em mente e com base nos dados acima para os visitantes que chegam ao meu site, o tempo médio para obter o arquivo CSS é de ~100ms e ~26% dos visitantes o obtêm a partir de seu cache local. Depois disso, é preciso buscar o(s) arquivo(s) de fonte desejado(s), que levam < 20ms na média – uma parcela significativa dos visitantes tem os arquivos em seu cache do navegador! Essa é uma ótima notícia e uma confirmação de que a estratégia do Google Fonts para recursos compartilhados de fontes está funcionando.
Seus resultados poderão variar de acordo com as fontes usadas, quantidade e tipo de tráfego, além de outras variáveis. A questão é que não temos que discutir em abstrato sobre os custos de latência e o desempenho de web fonts: temos as ferramentas e as APIs para medir as latências com precisão. E aquilo que podemos medir podemos otimizar.
O tempo limite de downloads de fontes lentas
Apesar de nossos melhores esforços para otimizar a entrega de recursos de fonte, por vezes, o usuário pode simplesmente ter uma má conexão devido a um link congestionado, má recepção, ou uma variedade de outros fatores. Nesse caso, os recursos críticos – incluindo downloads de fontes – podem bloquear a renderização da página, o que só piora a situação. Para lidar com isso, e especificamente para web fonts, navegadores diferentes têm tomado caminhos diferentes:
- O IE renderiza imediatamente o texto com a fonte de fallback e renderiza novamente assim que o download da fonte é concluído.
- O Firefox detém a renderização de fonte por até 3 segundos, após os quais ele usa uma fonte de fallback e, assim que o download da fonte termina, ele renderiza o texto mais uma vez com a fonte baixada.
- Chrome e Safari esperam até que o download da fonte tenha terminado.
Há muitos bons argumentos a favor e contra cada uma das estratégias, e não vamos entrar nessa discussão aqui. Dito isso, eu acho que a maioria vai concordar que a falta de um tempo limite de espera no Chrome e no Safari não é uma boa abordagem, e isso é algo que a equipe do Chrome vem investigando há algum tempo. Qual deve ser o valor do tempo limite? Para responder a isso, temos instrumentado o Chrome para reunir tamanhos de fontes e tempos de busca, o que rendeu os seguintes resultados:
Webfont size range | Percent | 50th | 70th | 90th | 95th | 99th |
0KB – 10KB | 5.47% | 136 ms | 264 ms | 785 ms | 1.44 s | 5.05 s |
10KB – 50KB | 77.55% | 111 ms | 259 ms | 892 ms | 1.69 s | 6.43 s |
50KB – 100KB | 14.00% | 167 ms | 882 ms | 1.31 s | 2.54 s | 9.74 s |
100KB – 1MB | 2.96% | 198 ms | 534 ms | 2.06 s | 4.19 s | 10+ s |
1MB+ | 0.02% | 370 ms | 969 ms | 4.22 s | 9.21 s | 10+ s |
Primeiro, a boa notícia é que a maioria das web fonts são relativamente pequenas (< 50KB). Em segundo lugar, a maioria dos downloads das fontes terminam dentro de várias centenas de milissegundos: a escolha de um tempo limite de 10s impactaria ~0,3% das requisições de fonte e um tempo limite de 3 elevaria para ~1,1%. Com base nesses dados, a conclusão foi fazer o Chrome espelhar o comportamento Firefox: timeout após 3 segundos e usar uma fonte de fallback e renderizar novamente, uma vez que o download da fonte for concluído. Esse comportamento será lançado no Chrome M35 e espero que o Safari siga o exemplo.
Na prática: iniciando requisições de recursos da fonte
Nós cobrimos como medir a latência de busca de cada recurso, mas há mais uma variável que é muitas vezes omitida e esquecida: também precisamos otimizar quando a busca é iniciada. Isso pode parecer óbvio, exceto que pode ser um desafio complicado para as fontes da web em particular.
Vamos dar uma olhada em um exemplo prático:
@font-face { font-family: 'FontB'; src: local('FontB'), url('http://mysite.com/fonts/fontB.woff') format('woff'); } p { font-family: FontA }
<!DOCTYPE html> <html> <head> <link href='stylesheet.css' rel='stylesheet'> <!-- see content above --> <style> @font-face { font-family: 'FontA'; src: local('FontA'), url('http://mysite.com/fonts/fontA.woff') format('woff'); } </style> <script src='application.js' /> </head> <body> <p>Hello world!</p> </body> </html>
Há muita coisa acontecendo acima: temos um CSS externo e o arquivo JavaScript, e no bloco de CSS inline, duas declarações de fonte. Pergunta: quando é que os pedidos de fontes serão disparados pelo navegador? Vamos ver passo a passo:
- O parser do documento descobre o stylesheet.css externo e um pedido é disparado.
- O parser do documento processa o bloco CSS in-line que declara a FontA – estamos sendo inteligentes aqui, queremos que o pedido da fonte seja disparado o mais cedo possível. A não ser que ele não aconteça. Mais sobre isso em um segundo.
- O parser do documento bloqueia em um script externo: não podemos prosseguir até que a busca seja executada.
- Uma vez que o script é buscado e executado, terminamos a construção do DOM, o cálculo do estilo e do layout é executado, e nós finalmente despachamos o pedido da FontA. Nesse ponto, também podemos realizar a primeira pintura, mas não podemos processar o texto com a nossa fonte desejada, uma vez que o pedido da fonte ocorre “durante o voo”… dah.
A observação chave na sequência acima é que os pedidos de fontes não são iniciados até que o navegador saiba que a fonte é realmente necessária para renderizar algum conteúdo na página – por exemplo, nós nunca solicitaremos a FontB, já que não há conteúdo que faz uso dela no exemplo acima! Por um lado, isso é ótimo, uma vez que minimiza o número de downloads. Por outro lado, isso também significa que o navegador não pode iniciar a solicitação da fonte até que ela tenha tanto o DOM quanto o CSSOM e seja capaz de resolver quais fontes são necessárias para a página atual.
No exemplo acima, nosso JavaScript externo bloqueia a construção do DOM até que ele seja buscado e executado, o que também atrasa o download de fonte. Para corrigir isso, temos algumas opções à nossa disposição: (a) eliminar o JavaScript, ( b) adicionar um atributo async (se possível), ou (c) movê-lo para a parte inferior da página. No entanto, quanto mais padrão for a viagem, mais o download das fontes demorará para ser iniciado até que o navegador possa calcular a árvore de renderização. Para tornar a renderização das fontes mais rápida, precisamos otimizar o caminho crítico de renderização da página.
Dica: além de medir as latências das requisições relativas para cada recurso, podemos medir e analisar o tempo de início do pedido com o Resource Timing! Seguir esse timestamp nos permitirá determinar quando o pedido da fonte foi iniciado.
Otimizando a fonte para buscar no Chrome M33
O Chrome M33 recebeu uma otimização importante que melhorará significativamente o desempenho de renderização de fontes. A maneira mais fácil de explicar a otimização é olhar para um exemplo pré-M33 que ilustra o problema:
- Cálculo de Estilos concluído em ~840ms para o ciclo de vida da página.
- Layout é acionado em ~1040ms, e a requisição de fonte é disparada imediatamente depois.
Por que esperamos pelo layout se já resolvemos os estilos duzentos milissegundos antes? Uma vez que sabemos os estilos, podemos descobrir quais fontes vamos precisar e imediatamente iniciar as solicitações apropriadas – esse é o novo comportamento no Chrome M33! Na superfície, essa otimização pode não parecer muito, mas com base nas medições do Chrome a lacuna entre estilo e layout é realmente muito maior do que se poderia pensar:
Percentile | 50th | 60th | 70th | 80th | 90th |
Time from Style → Layout | 132 ms | 182 ms | 259 ms | 410 ms | 820 ms |
Ao disparar os pedidos de fonte imediatamente após os cálculos do primeiro estilo, o download da fonte será iniciado ~130ms antes na mediana e ~800ms antes no percentil 90! Cruzando essas economias com a latência das buscas pelas fontes, vimos anteriormente que, em muitos casos, isso vai nos permitir buscar a fonte antes que o layout seja feito, o que significa que não teremos que bloquear a renderização do texto em momento algum – essa é uma grande vitória de desempenho.
Claro, devemos também fazer uma pergunta óbvia… Por que é que o intervalo entre o cálculo dos estilos e do layout é tão grande? O primeiro lugar para começarmos a responder é no Chrome DevTools: capturar uma de linha do tempo e verificar as operações lentas (por exemplo, JavaScript de execução longa etc.) Então, se você estiver com espírito de aventura, vá para chrome ://tracing para dar uma olhada sob o capô – é bem possível que o navegador esteja simplesmente ocupado processando o layout da página.
Otimizando web fonts com a API Font Load Events
Finalmente, chegamos à parte mais interessante de toda essa história: a API Font Load Events. Em poucas palavras, essa API permite que possamos gerenciar e definir como e quando as fontes serão carregadas – podemos programar o download de fonte segundo a nossa vontade, podemos especificar como e quando a fonte será processada, e muito mais. Se você estiver familiarizado com a biblioteca JS Web Font Loader, então pense que essa API faz o mesmo e muito mais, mas implementada de forma nativa no navegador:
var font = new FontFace("FontA", "url(http://mysite.com/fonts/fontA.woff)", {}); font.ready().then(function() { // font loaded.. swap in the text / define own behavior. }); font.load(); // initiate immediate fetch / don't block on render tree!
A API Font Load Events nos dá controle completo sobre quais fontes serão utilizadas, quando serão trocadas (ou seja, elas bloqueiam a renderização) e quando serão baixadas. No exemplo acima, construímos um objeto FontFace diretamente em JavaScript e desencadeamos uma requisição imediata – podemos sequenciar esse trecho no topo da página e evitar o bloqueio do CSSOM e DOM inteiramente! O melhor de tudo, você já pode brincar com essa API com nas versões Canary do Chrome e, se tudo correr bem, ele deve encontrar o seu caminho para a versão estável lá pelo M35.
Checklist do desempenho das web fonts
Web fonts web oferecem uma série de benefícios: melhoram a legibilidade, acessibilidade (pesquisável, selecionável, com zoom), branding e, quando bem feitas, belos resultados. Não é uma questão de se web fonts da web devem ser usadas, mas como otimizar seu uso. Para esse fim, uma lista rápida de verificação de desempenho:
- Auditar o uso de fontes e mantê-las enxutas.
- Certificar-se de que os recursos da fonte estão otimizados – veja dicas do Google Web Fonts.
- Medir seus recursos de fonte com Resource Timing: measure → optimize.
- Otimizar a latência de transferência e o tempo de busca inicial para cada fonte.
- Otimizar seu caminho crítico de transferência, eliminar JS desnecessário etc.
- Passar algum tempo brincando com a API de Font Load Events.
Só porque a página está usando uma web font, ou várias, não significa que ela vai (ou deve) tornar a renderização mais lenta. Um site bem otimizado pode oferecer uma experiência melhor e mais rápida ao usar web fonts.
***
Artigo traduzido pela Redação iMasters, com autorização do autor. Publicado originalmente em https://www.igvita.com/2014/01/31/optimizing-web-font-rendering-performance/