Geek #17

E se juntássemos os feras que trabalham em grandes portais para criar o guia definitivo de performance para front-end?

Mas nada daqueles guias chatos feitos para robôs, que tal fazer algo divertido? Que tal juntar Briza Bueno (Americanas.com), Davidson Fellipe (Globo.com), Giovanni Keppelen (ex-Peixe Urbano), Jaydson Gomes (Terra), Marcel Duran (Twitter), Mike Taylor (Opera), Renato Mangini (Google) e Sérgio Lopes (Caelum) para criar a melhor referência possível?

Foi isso mesmo que fizemos! E iremos te ajudar nessa batalha de criar sites cada vez mais rápidos.

Zeno Rocha, líder do projeto.

Performance importa mesmo?

??

É claro que importa e você sabe disso. Então por que continuamos fazendo sites lentos que prejudicam a experiência do usuário? Esse é um guia prático voltado para comunidade que vai te mostrar como fazer sites mais rápidos. Não vamos perder tempo mostrando cases milionários de performance, vamos direto ao ponto!

HTML

Evite código inline/incorporado

Existem três formas básicas de você lidar com CSS e Javascript:

1) Inline: onde o CSS é definido dentro do atributo style e o JavaScript dentro de um atributo onclick por exemplo, em qualquer tag HTML;

2) Incorporado: onde o CSS é definido dentro da tag <style> e o JavaScript dentro da tag <script>;

3) Externo: onde o CSS é carregado na tag <link> e o JavaScript através do atributo src da tag <script>.

As duas primeiras opções, apesar de reduzirem o número de requisições, aumentam o tamanho do documento HTML. Elas podem ser interessante quando você tem arquivos pequenos e o custo de uma requisição é maior. Nesse caso, faça testes para avaliar se há realmente ganho. Avalie também o objetivo da página e sua audiência, se o esperado é que usuários visitem essa página somente uma única vez, como por exemplo uma campanha temporária onde não se espera futuras visitas à mesma, utilizar inline/incorporado ajudará na redução do número de requisições.

> Evite escrever manualmente o CSS/JS no meio do HTML (use uma ferramenta que automatize esse processo).

Já a terceira opção, não só melhora a organização do seu código, como também possibilita que eles sejam armazenados no cache do navegador. Prefira essa opção para a maior parte dos casos, quando lidamos com arquivos grandes e muitas páginas.

> Ferramentas úteis / Referências

Estilos no topo, Scripts no rodapé

Quando colocamos as folhas de estilo no <head> permitimos que a página renderize progressivamente, o que causa uma percepção ao usuário de que está carregando mais rápido.

<head>
  <meta charset="UTF-8">
  <title>Browser Diet</title>

  <!-- CSS -->
  <link rel="stylesheet" href="style.css" media="all">
</head>

Enquanto que se colocássemos próximo ao fim da página, ela seria renderizada sem estilo algum até que o download do CSS terminasse.

Geek #32

Com relação ao JavaScript, é importante manter a chamada dos scripts no fim da página, já que eles bloqueiam a renderização da página enquanto são carregados e executados.

<body>
  <p>Lorem ipsum dolor sit amet.</p>

  <!-- JS -->
  <script src="script.js"></script>
</body>

> Referências

experimente o async & defer

Para explicar como esse atributo é útil para melhorar a performance, é melhor entender o que acontece quando não utilizamos ele.

Geek #20
<script src="example.js"></script>

Dessa forma, a página aguarda o script terminar de carregar antes de continuar sua renderização e sua execução é feita imediatamente após. Isso pode aumentar significativamente o tempo de carregamento da página. Algumas vezes esse comportamento é desejado, outras vezes não.

<script async src="example.js"></script>

O download do script é feito de forma assíncrona enquanto o processo de renderização da página continua a ser feito. O script é executado depois que o download estiver completo. Tenha em mente que caso tenha vários scripts com async, eles serão executados em nenhuma ordem específica.

> Referências

CSS

Comprima sua folha de estilo

Para manter um código legível é bom escrever comentários e ter cuidado com indentação.

.center {
  width: 960px;
  margin: 0 auto;
}

/* --- Structure --- */

.intro {
  margin: 100px;
  position: relative;
}

Só que para a máquina, nada disso importa. Por isso, lembre-se de comprimir seu CSS através de uma ferramenta automatizada.

.center{width:960px;margin:0 auto}.intro{margin:100px;position:relative}

Isso irá economizar muitos e muitos bytes, acelerando assim o download, análise e tempo de execução.

Para aqueles que usam pré-processadores como Sass, Less e Stylus, é possível configurar a compilação para gerar o código já minificado.

> Ferramentas úteis / Referências

Combine vários arquivos CSS em um só

Outra boa prática para organização e manutenção é separar seu estilo em diversos arquivos.

Geek #9
<link rel="stylesheet" href="structure.css" media="all">
<link rel="stylesheet" href="banner.css" media="all">
<link rel="stylesheet" href="layout.css" media="all">
<link rel="stylesheet" href="component.css" media="all">
<link rel="stylesheet" href="plugin.css" media="all">

Porém, é preciso realizar uma requisição HTTP para cada um dos arquivos e sabemos que o navegador não consegue lidar com muitos downloads paralelos.

<link rel="stylesheet" href="main.css" media="all">

Logo, combine seu CSS. Quanto menor for o número de arquivos, menor será o número de requisições feitas e mais rápida sua página carregará.

Quer ter o melhor dos dois mundos? Automatize esse processo através de uma ferramenta de build.

> Ferramentas úteis / Referências

Prefira <link> a @import

Há duas maneiras de incluir uma folha de estilo externa na sua página, pode ser através da tag <link>:

<link rel="stylesheet" href="style.css">

Ou da propriedade @import dentro de uma folha de estilo externa ou inline em uma tag <style>:

@import url('style.css');

Quando você utiliza a segunda opção a partir de uma folha de estilo externa, o navegador é incapaz de realizar o download em paralelo, o que causa atraso na cascata de carregamento dos arquivos.

> Referências

JavaScript

Carregue código de terceiros de forma assíncrona

Quem nunca carregou um código de terceiros para embedar um vídeo do YouTube ou colocar um botão de like/tweet?

O grande problema é que nem sempre esses códigos são entregues de maneira eficiente, seja pela conexão do usuário, ou pela conexão no servidor onde estão hospedados. Ou esse serviço pode estar temporariamente fora do ar ou mesmo ser bloqueado pelo firewall do usuário ou da empresa dele.

Geek #46

Para evitar que isso se torne um ponto crítico no carregamento de um site ou, pior, trave o carregamento da página toda, sempre carregue estes códigos de forma assíncrona (ou então use Friendly iFrames).

var script = document.createElement('script'),
    scripts = document.getElementsByTagName('script')[0];
script.async = true;
script.src = url;
scripts.parentNode.insertBefore(script, scripts);

Se você quiser carregar múltiplos widgets de terceiros de forma assíncrona use esse script.

> Vídeo / Referências

Guarde o tamanho do Array

O loop é sem dúvida uma das partes mais importantes com relação a performance no JavaScript. Busque otimizar a lógica dentro de um loop para que cada iteração seja feita de forma eficiente.

Um modo para fazer isso é armazenando o tamanho do array que será percorrido, assim ele não precisa ser recalculado toda vez que o loop for iterado.

var arr = new Array(1000),
    len, i;

for (i = 0; i < arr.length; i++) {
  // Ruim - o tamanho precisa ser calculado 1000 vezes
}

for (i = 0, len = arr.length; i < len; i++) {
  // Bom - o tamanho só é calculado 1 vez e depois armazenado
}

> Resultado no JSPerf

> Observação: Embora engines de browsers modernos otimizam automaticamente esse processo, continua sendo uma boa prática atender o legado de browsers que ainda perduram.

Em iterações sobre coleções HTML como uma lista de Nodes (NodeList) geradas por exemplo através de document.getElementsByTagName('a'), isto é particularmente crítico e essencial. Essas coleções são consideradas "vivas", ou seja, são automaticamente atualizadas quando há alterações no elemento à qual pertencem.

var links = document.getElementsByTagName('a'),
    len, i;

for (i = 0; i < links.length; i++) {
  // Ruim - a cada iteração a lista de links será recalculada para verificar se houve mudança
}

for (i = 0, len = links.length; i < len; i++) {
  // Bom - o tamanho da lista é primeiramente obtido e armazenado, depois comparado a cada iteração
}

// Péssimo: exemplo de loop infinito
for (i = 0; i < links.length; i++) {
  document.body.appendChild(document.createElement('a'));
  // a cada iteração a lista de links aumenta, nunca satisfazendo a condição de término do loop
  // isso não aconteceria se o tamanho da lista fosse armazenado e usado como condição
}

> Referências

Evite document.write

O uso do document.write faz com que a página fique na dependência do seu retorno para ser completamente carregada.

Essa (má) prática já foi abolida há anos pelos desenvolvedores, mas ainda existem casos onde seu uso ainda é necessário, como no fallback síncrono de algum arquivo JavaScript.

O HTML5 Boilerplate, por exemplo, faz o uso desta técnica para carregar o jQuery localmente, caso a CDN do Google não responda.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script>window.jQuery || document.write('<script src="js/vendor/jquery-1.9.0.min.js"><\/script>')</script>

> Atenção: document.write executado durante ou após o evento window.onload substitui todo o conteúdo da página atual.

<span>foo</span>
<script>
  window.onload = function() {
    document.write('<span>bar</span>');
  };
</script>

O resultado da página final será somente bar e não foobar como esperado. O mesmo ocorre quando executado após o evento window.onload.

<span>foo</span>
<script>
  setTimeout(function() {
    document.write('<span>bar</span>');
  }, 1000);
  window.onload = function() {
    // ...
  };
</script>

O resultado será o mesmo do exemplo anterior caso a função agendada pelo setTimeout seja executada após evento window.onload.

> Referências

Minimize Repaints e Reflows

Repaints e reflows são causados quando existe alguma re-renderização no DOM quando determinada propriedade ou elemento é alterado.

Repaints são disparados quando a aparência de um elemento é alterada sem alterar seu layout. Nicole Sullivan descreve isso como uma mudança de estilo como o ato de alterar um background-color.

Reflows são os mais custosos, causados quando as mudanças alterando o layout da página, como por exemplo alterar o width de um elemento.

Não há dúvida que ambos reflows e repaints excessivos devem ser evitados, portanto ao invés de fazer isso:

Geek #55
var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = div.offsetWidth + 'px';
}

Faça isso:

var div = document.getElementById("to-measure"),
    lis = document.getElementsByTagName('li'),
    widthToSet = div.offsetWidth,
    i, len;

for (i = 0, len = lis.length; i < len; i++) {
  lis[i].style.width = widthToSet + 'px';
}

Quando você define o style.width, o navegador precisa recalcular o layout. Normalmente, uma mudança de estilo em muitos elementos realiza apenas um reflow, já que o navegador não precisa pensar sobre ele até que necessite atualizar a tela. Entretanto, no primeiro exemplo, nós pedimos por offsetWidth, que é a largura de layout do elemento, então o navegador precisa recalcular o layout.

Se você precisar ler informações do layout na página, faça de uma vez só antes de definir qualquer coisa que altere o layout, como no segundo exemplo.

> Demo / Referências

Evite manipulações desnecessárias no DOM

Toda vez que você toca no DOM sem realmente precisar, um panda morre.

Sério, navegar por elementos do DOM é custoso. Apesar das engines JavaScript estarem cada vez mais poderosas e rápidas, prefira sempre otimizar ao máximo as consultas na árvore do DOM.

Uma das alternativas mais simples de serem adotadas é: quando um elemento precisar ser acessado mais de uma vez, guarde-o numa váriavel, e assim você não vai fazer mais do que uma consulta no DOM.

// muito ruim!
for (var i = 0; i < 100; i++) {
  document.getElementById("myList").innerHTML += "<span>" + i + "</span>";
}
// bem melhor :)
var myList = "";

for (var i = 0; i < 100; i++) {
  myList += "<span>" + i + "</span>";
}

document.getElementById("myList").innerHTML = myList;
// bem bem melhor :)
var myListHTML = document.getElementById("myList").innerHTML;

for (var i = 0; i < 100; i++) {
  myListHTML += "<span>" + i + "</span>";
}

> Resultados no JSPerf

Comprima seu script

Assim como no CSS, para manter um código legível é bom escrever comentários e ter cuidado com indentação.

BrowserDiet.app = function() {

  var foo = true;

  return {
    bar: function() {
      // do something
    }
  };

};

Só que para a máquina, nada disso importa. Por isso, lembre-se de comprimir seu JavaScript através de uma ferramenta automatizada.

BrowserDiet.app=function(){var a=!0;return{bar:function(){}}}

Isso irá economizar muitos e muitos bytes, acelerando assim o download, análise e tempo de execução.

> Ferramentas úteis / Referências

Combine vários arquivos JS em um só

Outra boa prática para organização e manutenção é separar seu script em diversos arquivos.

Geek #47
<script src="navbar.js"></script>
<script src="component.js"></script>
<script src="page.js"></script>
<script src="framework.js"></script>
<script src="plugin.js"></script>

Porém, é preciso realizar uma requisição HTTP para cada um dos arquivos e sabemos que o navegador não consegue lidar com muitos downloads paralelos.

<script src="main.js"></script>

Logo, combine seu JS. Quanto menor for o número de arquivos, menor será o número de requisições feitas e mais rápida sua página carregará.

Quer ter o melhor dos dois mundos? Automatize esse processo através de uma ferramenta de build.

> Ferramentas úteis / Referências

jQuery

Use sempre a última versão do jQuery

Os membros do core do jQuery estão sempre buscando trazer novidades para a biblioteca. Estes ajustes, muitas vezes, estão relacionados a melhorias da legibilidade do código, novas funcionalidades e, principalmente, otimização de algoritmos.

Geek #36

Por isso use sempre a última versão do jQuery, que está sempre disponível aqui, caso você queira copiar para um arquivo local:

http://code.jquery.com/jquery-latest.js

Mas nunca referencie essa URL em uma tag <script>: isso pode criar problemas no futuro, já que novas versões são automaticamente servidas no seu site antes mesmo de você ter a chance de testá-las. Em vez disso, use a última versão específica do jQuery que você precisa.

<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Já dizia o sábio Barney Stinson: "New is always better" :P

> Referências

Seletores

Seletores é uma das questões mais importantes no uso de jQuery. Há muitas maneiras diferentes de se selecionar um elemento no DOM, mas isso não significa que eles têm o mesmo desempenho, você pode selecionar um elemento usando classes, IDs ou métodos como find(), children().

Entre todos eles, selecionar um ID é o mais rápido, porque é baseado em uma operação DOM nativa.

$("#foo");

> Resultado on JSPerf

Use for ao invés de each

O uso das funções nativas do JavaScript, quase sempre, trazem execuções mais rápidas que as respectivas em jQuery. Sendo assim, ao invés de utilizar o método jQuery.each, use o for do próprio JavaScript.

Mas atenção, apesar do for in ser nativo, sua performance muitas vezes é pior que o jQuery.each.

Já o bom e velho for nos traz uma melhor forma de deixar nossas iterações mais rápidas, e você ainda pode aplicar um cache para o tamanho total do array e conseguirá aumentar o número de operações por segundo.

for ( var i = 0, len = a.length; i < len; i++ ) {
    e = a[i];
}

O uso de while reverso e for reverso é bem discutido na comunidade, e muitas vezes citado como a forma mais rápida de se fazer uma iteração com JavaScript, porém é bastante criticado devido ao fato de deixar o código com uma leitura mais difícil.

// while reverso
while ( i-- ) {
  // ...
}

// for reverso
for ( var i = array.length; i--; ) {
  // ...
}

> Resultado no JSPerf / Referências

Não use jQuery sempre...

Às vezes JavaScript puro pode ser mais fácil e mais performático que jQuery.

Geek #6

Considere o seguinte HTML:

<div id="text">Vamos mudar esse conteúdo.</div>

Ao invés de fazer isso:

$('#text').html('O conteúdo mudou').css({
  backgroundColor: 'red',
    color: 'yellow'
  });

Usamos JavaScript puro:

var text = document.getElementById('text');
text.innerHTML = 'O conteúdo mudou';
text.style.backgroundColor = 'red';
text.style.color = 'yellow';

É simples e muito mais rápido.

> Resultado no JSPerf / Referências

Imagens

Use CSS Sprites

Essa técnica consiste em agrupar diversas imagens em uma só.

CSS Sprite Example

E depois posicioná-las através de CSS.

.icon-foo {
  background-image: url('mySprite.png');
  background-position: -10px -10px;
}

.icon-bar {
  background-image: url('mySprite.png');
  background-position: -5px -5px;
}

Isso faz com que diminua absurdamente o número de requisições HTTP e evite atrasos no download de outros recursos da sua página.

Ao montar seu sprite, evite deixar muito espaço em branco entre as imagens, isso não afeta o peso do arquivo, mas sim o uso de memória para processar o mapa de pixels.

Apesar de extremamente difundida, essa técnica é pouco usada já que os desenvolvedores não utilizam ferramentas que automatizam o processo de geração dessa imagem. Por isso, separamos algumas que podem te ajudar nisso.

> Ferramentas úteis / Referências

Data URI

Essa técnica é uma alternativa para o uso de CSS sprites.

Data-URI é uma maneira de adicionar conteúdo inline de uma URI que você normalmente iria apontar. Nesse exemplo, nós estamos utilizando para adicionar conteúdo inline no background de uma imagem no CSS, a fim de reduzir o número de requisições HTTP necessárias para carregar uma página.

Antes:

.icon-foo {
  background-image: url('foo.png');
}

Depois:

.icon-foo {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAAACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII%3D');
}

Todos os navegadores do IE8 para cima suportam essa técnica de codificação base64.

Ambos esse método e CSS sprites precisam de ferramentas de build para serem de fácil manutenção. Esse método tem a vantagem de não exigir reposicionamento manual do sprite já que mantém suas imagens em arquivos individuais durante o desenvolvimento.

Entretanto tem a desvantagem de aumentar consideravelmente o tamanho do seu HTML/CSS se você possuir imagens grandes. Não é recomendado utilizar esse método se você não está utilizando a técnica de gzip no seu HTML/CSS durante as requisições HTTP já que a sobrecarga de tamanho pode anular os ganhos de velocidade sobre minimizar o número de requisições HTTP.

> Ferramentas úteis / Referências

Não escale imagens direto no código

Defina sempre o width e o height de uma imagem, isso irá reduzir o tempo de renderização evitando repaints e reflows desnecessários.

<img width="100" height="100" src="logo.jpg" alt="Logo">

Conhecendo esses atributos do HTML, Joãozinho que tem uma imagem de 700x700px resolve exibi-la com um tamanho de 50x50px.

O que o malandro Joãozinho não sabe é que uma dezena de KBs serão trafegados a mais pela rede, desnecessariamente.

Por isso, lembre-se: só porque você pode definir a altura e largura de uma imagem no HTML, não quer dizer que deve redimensionar imagens maiores do que precisa.

> Referências

Otimize suas imagens

Arquivos de imagens possuem muitos KB de informações desnecessárias na Web. Por exemplo, uma foto JPEG tem um monte de metadados Exif colocados pela câmera (data da foto, modelo da câmera, local etc). Um PNG possui uma série de informações sobre cores, metadados e, às vezes, até uma miniatura da imagem embutida no meio. Nada disso é importante para a renderização da imagem no navegador e só gasta bytes de rede.

Existem ferramentas de otimização de imagens que removem todas essas informações desnecessárias e te dão um arquivo mais enxuto mas visualmente idêntico. Elas fazem uma compressão sem perda de qualidade visual (chamamos de lossless).

Outra forma de otimizar imagens é comprimi-las com certas perdas visuais aceitáveis. Esse tipo de operação é chamada de otimização lossy. Quando você exportar um JPEG, por exemplo, pode escolher a qualidade (um número de 0 a 100). Pensando em performance, pegue sempre o número mais baixo onde a qualidade visual ainda é aceitável. Outra otimização lossy famosa é diminuir o número de cores na paleta de um PNG, ou transformar arquivos PNG-24 em PNG-8.

Para melhorar a performance percebida pelo usuário, você deve transformar todos seus arquivos JPEGs em progressivos. JPEGs progressivos têm ótimo suporte nos navegadores, são muito fáceis de criar e não têm nenhum problema de performance significativo. O bom é que a imagem vai aparecer bem antes na página (ver exemplo).

> Ferramentas úteis / Referências

Bônus

Ferramentas de diagnóstico: suas melhores amigas

Geek #43

Se você quer se aventurar nesse mundo, é imprescindível instalar a extensão YSlow no seu Chrome ou Firefox, elas serão suas melhores amigas a partir desse momento.

Ou se preferir ferramentas online, visite o WebPageTest, HTTP Archive ou PageSpeed.

Em geral elas analisam a performance do seu site, geram um relatório e dão uma nota para ele, sem contar nas dicas preciosas que apresentam para você resolver cada um dos problemas.

Por hoje é só!

Geek #31

Esperamos que depois desse guia você possa colocar seu site em forma :)

Lembre-se que, como tudo na vida, não existe bala de prata. Otimizar a performance da sua aplicação é muito legal, mas nem todas suas decisões de desenvolvimento devem ser baseadas nisso, às vezes é preciso sacrificar um lado para aprimorar outro.

Quer saber mais? Consulte as Referências que usamos para escrever esse guia.

Tem sugestões? Envie um tweet para @BrowserDiet ou um pull request no Github.

Compartilhe com seus amiguinhos, vamos criar uma web mais rápida para todos \o/