Queries CSS para quantidades

visualizações Publicado em: 05/08/2015

Crédito: essa matéria é uma tradução e foi escrita por Heydon Pickering. URL do original: Quantity Queries for CSS
Reprinted with the permission of A List Apart and the author.

Introdução

Este artigo é um guia para definição de breakpoints baseado em quantidades de elementos HTML na marcação, tal como você já vem fazendo com @media queries baseada na largura da viewport. Não estamos falando de uma especificação obscura e distante e nem em um devaneio de um desenvolvedor. Nós podemos usar esta funcionalidade hoje, com as CSS existentes.

Conteúdo dinâmico

A principal variável do web design responsivo é o espaço. Layouts responsivos são aqueles que acomodam consistentemente uma determinada quantidade de conteúdos em um espaço definido. O conteúdo é constante e o espaço é variável.

@media query é a "queridinha" do design responsivo pois é ela que permite ao desenvolvedor criar “breakpoints” sempre que um layout "quebra" por falta de espaço, permitindo assim rearranjar os conteúdos no novo espaço e obter um layout consistente. Contudo não é somente as dimensões da viewport, mas também a quantidade de conteúdo que pode "pressionar" o espaço.

Da mesma maneira que o usuário final pode estar usando dispositivos com uma infinidade de diferentes tamanhos de tela os editores de texto também podem adicionar ou remover conteúdos. É para isso que existe os gerenciadores de conteúdos. Esse enfoque faz com que mockups para páginas web criados com o Photoshop se tornem duplamente obsoletos: são snapshots para uma só largura de janela e mostram conteúdos em um só estado.

Neste artigo vamos mostrar uma técnica CSS baseada em quantidades usando seletores compostos especialmente formados para esta finalidade. Para demonstrar a técnica de uso de tais seletores vamos exemplificar como resolver um problema clássico em particular: como alterar o modo de exibição dos itens de um menu horizontal de modo a permitir a expansão dos itens em relação ao layout inicial do menu. Ou seja, vou demonstrar como passar do um modo de exibição display: table-cell para o modo display: inline-block a partir do momento em que o número de itens do menu seja “igual ou maior que 6.”

Não usaremos JavaScript ou qualquer outra lógica baseada em template e na marcação HTML do menu não será definido nenhum atributo classe. Usando somente CSS a técnica mostrada estará de acordo com o princípio da separação das camadas de desenvolvimento segundo o qual a HTML destina-se a marcação de conteúdos e as CSS a apresentação. Layout é por conta das CSS e sempre que possível somente CSS.

Observe a imagem mostrada a seguir que ilustra o menu que iremos usar nos exemplos constantes deste artigo.

Mostra a barra de menu inicial com menos de seis itens e a barra de menu expandida com seis ou mais itens

A demonstração prática do nosso menu está disponível no CodePen e a ele, menu, faremos referência no decorrer deste artigo.

Para melhor ilustrar a técnica de quantificação vou usar imagens de lulas para representar elementos HTML. Lulas na cor verde com marca "ticada" (um V) representam elementos HTML que casam com o seletor CSS em estudo, lulas na cor vermelha com marca "exclusão" (um X) representam elementos HTML que não casam com o seletor CSS em estudo e finalmente lulas na cor cinza representam elementos HTML que não existem.

Imagens de 3 lulas a serem usadas nos exemplos desta matéria. Lula verde (para elementos selecionados), lula vermelha (para elementos não selecionados) e lula cinza para elementos que não existem

Contagem

Para se determinar a quantidade de elementos em um determinado contexto devemos contá-los. As CSS não possuem uma API para contagem, contudo podemos resolver esse problema com uma engenhosa combinação de seletores.

Contagem para uma ocorrência

A pseudoclasse estrutural :only-child permite que se estilize elementos que ocorrem de forma única. Sua finalidade é selecionar um elemento quando ele é o único filho de um determinado elemento. A contagem de elementos casados por este seletor retorna 1 como total. Juntamente com a pseudoclasse estrutural :only-of-type estas são as únicas pseudoclasses estruturais que se baseiam em quantidade para retornar o elemento casado.

No exemplo a seguir usamos a pseudoclasse estrutural :only-of-type para estilizar de forma diferenciada o botão que é o único botão entre todos os filhos de um elemento-pai. Tais botões foram estilizados com tamanho de fonte maior com a finalidade de dar-lhes destaque tendo em vista sua singularidade (serem únicos) e portanto serem supostamente mais importantes entre os irmãos.

button {   	
  font-size: 1.25em;  
}    
  button:only-of-type { 
  font-size: 2em;  
}    

E, agora a parte crucial. Se nossa marcação prevê um botão único entre os elementos-filhos e futuramente tivermos que acrescentar mais botões antes ou depois do botão inicial, cada um dos botões serão estilizados com o tamanho de fonte menor, ou seja o botão inicial perderá sua estilização com fonte maior, pois ele perderá sua condição de único filho do tipo botão. A estilização de todos os botões do conjunto depende da quantidade de botões: se existirem "menos de dois" botões a estilização com tamanho de fonte maior será honrada. Observe as regras CSS novamente e o "diagrama de lulas" que se segue e tente imaginar como a estilização depende da quantidade de botões.

button {  	
  font-size: 1.25em;   
}    
  button:only-of-type {  	
  font-size: 2em;  
}    
Menos que dois seleciona um (lula verde) quando adiciona-se mais um os elementos são deselecionados (lulas vermelhas)

Se você achar mais natural pode-se inverter a lógica CSS usando a pseudoclasse de negação e alterando a condição de casamento do seletor para "mais do que um"

/* "mais do que um" resulta em tamanho de fonte menor */
  button {  	
  font-size: 2em;  
}    
  button:not(:only-of-type) {  	
  font-size: 1.25em;  
}    
Mais que um não seleciona nada (lula vermelha) quando adiciona-se mais um ambos os elementos são selecionados (lulas verdes)

Contagem para n ocorrências

Estilizar baseado em contagens "mais do que um" e "menos do que dois" é um truque interessante, mas uma forma de estilizar baseada em qualquer quantidade seria um truque muito mais interessante e flexível. Ou seja, um truque que possibilitasse estilizar baseado em contagem do tipo "maior ou igual a n" para qualquer valor de n. Desta forma seria possível estilizar nosso menu de navegação, mostrado anteriormente, com base em contagem "maior ou igual a 6".

Tendo em vista alcançar uma forma de estilizar como a descrita anteriormente que tal a possibilidade de estilizar para quantidades do tipo crescente, tal como: "exatamente 6" ou "exatamente 745"? Como conseguir isto? Preciso encontrar um seletor CSS capaz de retornar um conjunto de elementos composto que qualquer quantidade n de elementos.

Felizmente a pseudoclasse estrutural :nth-last-child(n) aceita o parâmetro "n", permitindo que se conte um conjunto de elementos começando do final para o início do conjunto. Por exemplo :nth-last-child(6) casa com o elemento que é o sexto elemento irmão contando-se do último para o primeiro.

As coisas começam a ficar interessantes quando concatenamos :nth-last-child(6) com :first-childcriando uma segunda condição. Neste caso o casamento do seletor será com o elemento que é ao mesmo tempo o sexto do final para o início e o primeiro elemento.

li:nth-last-child(6):first-child { 
	/* regras de estilo para a lula verde */
}    

Se tal elemento existir o casamento se dará com o primeiro elemento de um conjunto de elementos exatamente igual a seis em quantidade. Bem radical! encontrei um seletor CSS que retorna uma quantidade exata de elementos.

Existindo seis lulas a primeira é verde e as demais vermelhas. Para selecionar a primeira usa-se o seletor nth-last-child(6) e o seletor first-child

A partir daqui precisamos encontrar um seletor que case com os elementos restantes (não estilizados) do conjunto. Para isso podemos usar o sinal de combinação irmão geral.

Seis lulas verdes, pois a primeira lula verde foi combinada com o sinal de combinação irmão geral fazendo com que as lulas que se seguem fiquem verdes

Se você não esta familiarizado com o sinal de combinação irmão geral ( símbolo ~ ), no seletor li:nth-last-child(6):first-child ~ li o casamento se dá com "todos os elementos li que estão depois de li:nth-last-child(6):first-child." No exemplo mostrado a seguir cada um dos elementos assume a cor verde se o conjunto de elementos for exatamente igual a seis.

li:nth-last-child(6):first-child,   
li:nth-last-child(6):first-child ~ li {  	
  color: green;  
}    

Maior ou igual a 6

Casar quantidades pré-definidas tais como 6, 19 ou 653 não é especialmente útil, pois elas são próprias a situações específicas e determinadas. O mesmo ocorre com @media queries do design responsivo, onde casar quantidades pré-definidas com uso das propriedades min-width ou max-width como mostrado a seguir é igualmente inútil:

@media screen and (width: 500px) { 
	/* estilização para larguras de viewport exatamente igual a 500px */
}    

No nosso menu de navegação iremos alterar o layout baseado em um limite: a quantidade total de itens no menu. A proposta no exemplo é alterar a estilização do menu quando o número de itens for igual ou maior do que seis isto é, não somente quando for exatamente igual a seis itens. Ao ser alcançado o limite proposto para o número de itens (6 itens) o layout inicial do menu que é em formato de linha de tabela assume uma configuração de blocos do tipo inline para cada item. É importante ressaltar que a mudança do layout inicial para blocos inline seja honrada à medida que o número de itens vá aumentando a partir dos 6 itens.

A questão é: como construir um seletor CSS para atender a proposta de mudança de layout do menu? A resposta à questão está na funcionalidade offset.

O parâmetro n+6

A pseudoclasse estrutural :nth-child() admite como parâmetro um número expresso da forma "n + [inteiro]". Por exemplo :nth-child(n+6) casa com todos os elementos de um conjunto a partir do sexto elemento inclusive.

Conjunto de lulas vermelhas que se tornam verdes.

Agora sim, temos um método para estilizar que não depende de uma quantidade total fixa, ou seja, não estamos estilizando baseado no fato de que existem seis elementos. Estilizamos porque existem cinco ou mais elementos.

Começaremos a resolver o problema criando um seletor que exclua os cinco últimos itens. Usando o oposto de :nth-child(n+6) que é :nth-last-child(n+6) pode-se aplicar estilização ao layout do menu expandido selecionando os "últimos elementos" começando do sexto contado de trás para frente.

li:nth-last-child(n+6) { 
  /* regras CSS aqui */
}   

Este seletor exclui os cinco últimos elementos de qualquer conjunto de itens. Assim, quando o número de itens for menor do que seis nenhum item casa com o seletor. Trata-se de uma espécie de efeito CSS conhecido como "sliding doors".

Conjunto de lulas verdes à esquerda e vermelhas à direita que se transformam em lulas vermelhas quando o número de lulas é menor que seis

Então, se o total de itens for igual ou maior do que seis precisamos encontrar uma forma de estilizar aqueles cinco itens que permanecem sem estilização como mostrado no diagrama anterior. Isso é fácil: Se existem seis ou mais itens é certo que existe o item que casa com a pseudoclasse:nth-last-child(n+6). Para estilizar todos os itens que seguem tal casamento basta usar o sinal de combinação irmão geral ( símbolo ~ ).

Quando um conjunto de lulas vermelhas é acrescido de lulas as lulas da direita se tornam verdes e podem ser usadas para transformar o restante das lulas vermelhas em verde(com uso do sinal de combinação irmão geral)

E, a solução final para nosso problema é o seletor:

li:nth-last-child(n+6),  
li:nth-last-child(n+6) ~ li { 
/* regras CSS aqui */
}    

Obviamente o número de itens do menu igual a 6 que foi adotado no exemplo poderá ser substituido por qualquer número inteiro positivo até 653.279.

Menor ou igual a n

No exemplo mostrado anteriormente para a pseudoclasse estrutural :only-of-type é possível inverter a lógica, ali mostrada, para "maior ou igual a n" obtendo a condição "menor ou igual a n". Escolher qual das duas lógicas adotar é uma mera questão de definir qual é o estado inicial de estilização que lhe parece mais natural. "Menor ou igual a n" obtem-se fazendo n negativo e reinstanciando a condição :first-child como mostrado a seguir:

li:nth-last-child(-n+6):first-child,  
li:nth-last-child(-n+6):first-child ~ li { 
  /* regras CSS aqui */
}    

O uso do sinal "-" (negativo) inverte a direção da seleção: em lugar de selecionar do início para o fim seleciona do fim para o início. Convém notar que em ambos os casos a seleção inclui o sexto item.

nth-child versus nth-of-type

Observe que usamos nos exemplos anteriores :nth-child() e :nth-last-child() e não usamos :nth-of-type() e :nth-last-of-type(). Fizemos assim porque tratou-se de estilizar elementos li que são os únicos filhos legítimos de elementos ul, e em consequência tanto :last-child() como :last-of-type() poderiam ter sido usados.

Contudo, as pseudoclasses :nth-child() e :nth-of-type() casam com diferentes conjuntos de elementos e a escolha de uma ou outra depende do casamento de seletor que pretendemos fazer. Uma vez que :nth-child() não depende do tipo de elemento seu uso é indicado para percorrer e selecionar diferentes tipos de elementos irmãos como mostrado a seguir:

<div class="container">    	
  <p>...</p>    	
  <p>...</p>    	
  <blockquote>...</blockquote>    	
  <figure>...</figure>    	
  <p>...</p>    	
  <p>...</p>    
</div>
.container > :nth-last-child(n+3),  
.container > :nth-last-child(n+3) ~ * { 
  /* regras CSS aqui */
}    

(Notar o uso do seletor universal com a finalidade de fazer a seleção independentemente do tipo de elemento. O seletor :last-child(n+3) ~ * casa com "qualquer elemento de qualquer tipo que se segue ao elemento casado por :last-child(n+3).)

A vantagem de se usar :nth-last-of-type() é que o conjunto casado pelo seletor poderá ser filtrado retornando apenas os elementos irmãos de um determinado tipo quando existirem elementos irmão de diferentes tipos. No exemplo mostrado a seguir selecionou-se apenas os parágrafos dentro de um elemento-pai div ainda que exista um elemento div e um elemento blockquote dentro daquele elemento-pai.

<div class="container">    	
  <div>...</div>    	
  <p>...</p>    	
  <p>...</p>    	
  <p>...</p>    	
  <p>...</p>    	
  <p>...</p>    	
  <p>...</p>    	
  <p>...</p>    	
  <blockquote>...</blockquote>    
</div>    
p:nth-last-of-type(n+6),  
p:nth-last-of-type(n+6) ~ p { 
  /* Regras CSS aqui */
}   

Suporte a seletores

Todos os seletores das CSS2.1 e CSS3 usados neste artigo são suportados pelo Internet Explorer 9 e maiores assim como pelos modernos navegadores de dispositivos móveis.

O suporte oferecido pelo Internet Explorer 8 é razoável e parcial, assim, considere o uso de polyfill JavaScript se você pretende servir aquele navegador. Outra solução para resolver problemas de compatibilidade com navegadores é usar classes alternativas como descrito em IE9-specific classes. No caso do nosso menu uma opção segura é estilizar itens adicionados posteriormente como blocos inline conforme mostrado a seguir:

nav li:nth-last-child(n+6),  
nav li:nth-last-child(n+6) ~ li,     
.lt-ie9 nav li {  	
  display: inline-block; /* etc */
}    

No mundo real

Suponha que nosso menu de navegação faz parte de um site alimentado por um gerenciador de conteúdos. Dependendo do administrador do tema do site o menu será composto de alguns itens essenciais tais como “Home” e “About” ou por vários itens contemplando páginas personalizadas e opções de categorias.

A estilização do menu com layouts diferentes baseados no número de itens presentes no menu cria uma solução elegante e flexível capaz de comportar diferentes implementações do menu: com isso criamos uma estilização baseada na variação de conteúdos de maneira análoga àquela que fazemos quando implementamos estilização baseados em largura da janela do navegador.

Mostra a barra de menu inicial com menos de seis itens e a barra de menu expandida com seis ou mais itens

É isso: saudação às lulas! Agora você tem mais uma arma no seu arsenal de desenvolvedor, pois conhece um mecanismo capaz de estilizar baseado em quantidades.

Design independente do conteúdo

Web design responsivo resolve um problema importante: faz com que o mesmo conteúdo seja servido de forma consistente em diferentes dispositivos de usuário. Servir diferentes conteúdos para diferentes dispositivos é uma prática inaceitável. De maneira similar é inaceitável que o design determine a natureza do conteúdo. Não se espera que um autor esconda conteúdos baseado no dispositivo. Tal prática resulta em design projetado de forma errada.

A forma assumida pelo conteúdo bem como a quantidade de conteúdo apresentada é desconhecida e indeterminada. Não podemos nos apoiar em soluções que engessam e truncam conteúdos com uso de scripts. Para o correto manuseio de conteúdos é necessário desenvolver novas ferramentas e técnicas. Queries baseadas em quantidades é uma destas novas técnicas.

Projetar para a web envolve comprometimento com a mutabilidade, com diferenças e incertezas. Trata-se do desconhecido. É antes de tudo um projeto visual e não a manifestação de um formato e a antecipação dos diferentes formatos que uma coisa poderá assumir. Para alguns isso pode parecer completamente sem nexo, mas para você e para mim trata-se de um excitante desafio a vencer.

X

Matérias recomendadas

A sintaxe da regra CSS

Seletores CSS3