Desenvolvimento

21 mai, 2014

Otimização do desempenho da renderização de Web Fonts

Publicidade

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:

  1. O número total de fontes e o peso das fontes utilizadas na página.
  2. O tamanho total dos bytes das fontes usadas na página.
  3. A latência da transferência do recurso da fonte.
  4. 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:

web-fonts-1

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:

  1. O parser do documento descobre o stylesheet.css externo e um pedido é disparado.
  2. 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.
  3. O parser do documento bloqueia em um script externo: não podemos prosseguir até que a busca seja executada.
  4. 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:

web-fonts-2

  1. Cálculo de Estilos concluído em ~840ms para o ciclo de vida da página.
  2. 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:

  1. Auditar o uso de fontes e mantê-las enxutas.
  2. Certificar-se de que os recursos da fonte estão otimizados – veja dicas do Google Web Fonts.
  3. Medir seus recursos de fonte com Resource Timing: measure → optimize.
  4. Otimizar a latência de transferência e o tempo de busca inicial para cada fonte.
  5. Otimizar seu caminho crítico de transferência, eliminar JS desnecessário etc.
  6. 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/