Escrever CSS eficiente não é um assunto novo, mas algo que desenvolvedores front-end devem prestar bastante atenção, principalmente porque, como devem saber, pelo menos 80% das questões de performance de web sites se encontram no front-end. Portanto, é interessante e, em alguns casos, vital, para o sucesso de um site, que os seletores CSS sejam escritos e otimizados de modo a garantir a melhor performance possível!
As dicas e regras apresentadas neste artigo se aplicam, principalmente, a sites de alto desempenho, nos quais a velocidade é uma característica imprescindível e 1000 elementos podem estar presentes no DOM. Mas não importa se você está construindo o próximo Facebook ou um site para o decorador local: as melhores práticas são as melhores práticas.
Seletores CSS
Seletores CSS não são novidade para nós, desenvolvedores web. Os seletores mais básicos são de tipo (ex. div
), ID (ex. #header
) e classe (ex. .tweet
), respectivamente. Os mais incomuns incluem pseudo-classes comuns (ex. :hover
) e seletores CSS3 mais complexos, incluindo :first-child
ou [class^="grid-"]
.
Seletores têm uma eficiência inerente que, partindo dos mais eficientes para os menos eficientes, são:
- ID, ex.
#header
- Classe, ex.
.promo
- Tipo, ex.
div
- Irmão adjacente, ex.
h2 + p
- Filho, ex.
li > ul
- Descendente, ex.
ul a
- Universal, ex.
*
- Atributo, ex.
[type="text"]
- Pseudo-classe/Pseudo-elemento, ex.
a:hover
É importante notar que, apesar de, tecnicamente, um ID ser mais rápido e mais performático, essa diferença é mínima. Conforme já foi explicado no artigo “Não use IDs como seletores em CSS“, um seletor ID e um seletor de classe mostram muito pouca diferença no quesito performance.
Combinando seletores CSS
É possível ter seletores independentes, como #nav
, que irão selecionar qualquer elemento com um ID “nav”, ou você pode combinar seletores, como #nav a
, que vai corresponder a qualquer link/âncora dentro de qualquer elemento de ID de “nav” – que, no caso, seria somente 1 por página, conforme visto no artigo sobre diferenças entre IDs e classes.
Pode parecer estranho e um pouco difícil de entender rapidamete, mas, diferentemente do que a maioria dos ocidentais, que costumamos ler da esquerda para a direita, seletores CSS são lidos da direita para a esquerda pelos navegadores. Para tentar compreender o motivo pelo qual os browsers fazem isso, lembremo-nos daqueles jogos de “encontre o caminho” que brincávamos nas revistas da nossa infância. Todos nós iniciávamos a resolução pelo objetivo final do emaranhado de caminhos, retrocedendo até um dos pontos iniciais possíveis.
É mais eficiente para um navegador começar sua procura por combinações a partir do elemento mais à direita (o que ele sabe que irá receber o estilo) e trabalhar o seu caminho de volta através da árvore de DOM do que começar no alto da árvore de DOM e fazer o caminho para baixo, que poderia nem mesmo acabar no seletor que precisa receber a estilização – também conhecido como seletor-chave.
Já deu pra ter uma noção, mas, caso queira uma explicação mais detalhada, confira a discussão “CSS Selectors parsed right to left. Why?“, no Stack Overflow.
O seletor-chave
O seletor-chave, como discutido, é a parte mais à direita de um seletor CSS. Isto é o que o browser procura em primeiro lugar. Lembre-se da ordem dos seletores mais eficientes que abordamos anteriormente. Seja qual for o seletor chave da vez, ao escrever CSS eficiente é este seletor que contém o segredo do alto desempenho!
Um seletor como:
1 |
#content .intro {} |
O navegador irá procurar todas as instâncias de .intro
e, depois, começar a subir no DOM até encontar o elemento #content
.
No entanto, o seguinte seletor não é muito performático:
1 |
#content * {} |
O que isso faz é olhar para cada elemento da página (para cada um, mesmo!) e, em seguida, checar se qualquer um deles é descendente de #content
. Este é um seletor de performance ruim e, como seletor-chave, é muito “expensivo”. Usando este conhecimento, é possível tomar melhores decisões quanto ao modo de classificar e selecionar elementos.
Vamos dizer que, numa página realmente grande, de um site realmente enorme, há centenas ou, mesmo, milhares de elementos a
. Há, também, uma pequena seção de links de mídia social em um ul
com um ID “social”; digamos que Twitter, Facebook e Google+. Temos 3 links de mídias sociais nesta página e, além destes, incontáveis outras âncoras.
Portanto, seria excessivamente custoso e nada performático um seletor como:
1 |
#social a {} |
O que vai acontecer, aqui, é que o navegador irá avaliar todos os milhares de links na página antes de fazer o match com os links de #social
. O seletor-chave corresponde a muitos outros elementos que nada têm a ver com a estilização pretendida.
Para remediar a situação, uma solução possível seria adicionar um seletor mais específico e explícito, .social-link
, para cada um dos a
da “área social”. Mas isso iria contra o fato de que não é aconselhado colocar classes desnecessárias na marcação quando é possível usar uma solução mais “enxuta”.
É por isso que o desenvolvimento web focado em desempenho é algo tão interessante, um estranho equilíbrio entre melhores práticas de padrões web e performance!
Considere o seguinte código:
1 |
Com esse CSS:
1 |
#social a {} |
Agora, veja este outro HTML:
1 |
Com esse CSS:
1 |
#social .social-link {} |
Esse novo selector-chave vai corresponder a poucos elementos, o que significa que o navegador conseguirá realizar a estilização mais rapidamente e passar para a próxima coisa a ser feita.
Recapitulando, o seletor-chave é o que determina quanto trabalho o navegador terá de fazer; portanto, tenha bastante atenção nele(s)!
Superqualificando seletores
Já sabemos o que é um seletor-chave e que é nele que a maior parte do trabalho está. A boa notícia é que é possível trabalhar para o(s) otimizar ainda mais! A melhor coisa sobre ter bons seletores-chave explícitos e criar CSS eficiente e de qualidade é que é possível evitar a “superqualificação” de seletores. Um seletor superqualificado se parece com:
1 |
html body .wrapper #content a {} |
O navegador tem que olhar para todos os elementos a
, então, verificar se, respectivamente, está em um elemento de ID “content” e assim por diante, num caminho até chegar em html
. Isso está fazendo com que o navegador passe por caminhos que, realmente, ele não precisa e, pelo menos 3 desses elementos no seletor, são totalmente desnecessários. A regra poderia ser reescrita para:
1 |
#content a {} |
Um outro exemplo, infelizmente bastante comum, é:
1 |
ul#nav li a {} |
Sabemos que, se o a
está dentro de li
, que tem que estar dentro de #nav
, então é possível, logo de cara, eliminar o li
do seletor. Então, como o #nav
é um ID (e já sabemos que só podem existir IDs únicos e exclusivamente nominados na página), o elemento ul
também pode ser eliminado. O que reduz o seletor para:
1 |
#nav a {} |
Seletores superqualificados fazem com que o navegador trabalhe mais do que precisa e deveria. Construa seletores mais enxutos e de alto desempenho eliminando as partes desnecessárias.
Toda essa otimização CSS é necessária?
Independentemente do tamanho do seu projeto, os navegadores interpretação de forma igual e terão o mesmo trabalho de análise das regras CSS. Portanto, se estiver escrevendo algo como:
1 |
div:nth-of-type(3) ul:last-child li:nth-of-type(odd) * { font-weight: bold } |
Você, muito provavelmente, está fazendo a coisa errada!
Mas, se seu humilde projeto web, de uma hora para a outra, começa a expandir e a ter, cada vez mais, acessos e mais acessos, crescendo exponencialmente e acima do esperado, tornando-se “o próximo hit da web”, os recursos despendidos para otimizar o CSS, depois disso, serão o melhor investimento a ser feito?
Será que, à época, não haverá arrependimentos por não ter escrito um bom CSS desde o começo? Fica a questão…