Quantity Queries per CSS

Non odiate anche voi i documentari che alla fine non mantengono le promesse? Hanno titoli attraenti come Alla ricerca del Calamaro Gigante e vi stuzzicano con inquadrature sottomarine di forme misteriose e vi fanno vedere scienziati esaltati che indicano un non meglio precisato punto, laggiù nel mare. Vi mettete comodi per guardare questo documentario, aguzzate la vista con sospetto, pensando “Sarà meglio che io veda qualche calamaro o qualcuno di questo newtork riceverà una lettera che non dimenticherà facilmente.”

L’articolo prosegue sotto

Ovviamente, dopo 90 minuti di interviste con pescatori annoiati, il presentatore è costretto a concludere “No… no, non abbiamo trovato nessun calamaro gigante. Ma magari un giorno [orchestra suona musica maestosa].” Grandioso. Volevate Alla ricerca di Nemo e invece vi hanno dato Non abbiamo trovato Nemo.

Io non vi farei mai una cosa del genere, amici. Questa è la vostra guida alla creazione di breakpoint di stile per quantità di elementi HTML, più o meno come fate già con le @media queries per le dimensioni della viewport. Non vi indirizzerò verso specifiche fumose o a un vago pensiero di uno sviluppatore. Le creiamo oggi, con quello che CSS mette già a nostra disposizione.

Contenuto dinamico#section1

Principalmente il Responsive web design ruota attorno ad una variabile: lo spazio. Per testare i layout responsive, prendiamo una certa quantità di contenuto e vediamo a quale spazio si adatta bene. Il contenuto viene considerato una costante, lo spazio una variabile.

La @media query è la beniamina del responsive web design perché ci permette di inserire dei “breakpoints” ovunque non funzioni più la strategia di layout e andrebbe meglio un altro layout. Tuttavia, non sono solo le dimensioni della viewport che possono mettere sotto pressione lo spazio, ma anche la quantità di contenuto.

Proprio come i vostri utenti finali possono usare device con una moltitudine di dimensioni di schermo diverse, così i vostri content editor potranno aggiungere o rimuovere il contenuto. I content management systems servono proprio a questo. Ciò rende due volte vecchi i mockup Photoshop delle pagine web: sono solo un’istantanea di un’unica viewport, con il contenuto in un unico stato.

In questo articolo, delineerò una tecnica per rendere CSS quantity-aware (consapevole della quantità) utilizzando dei selettori formati ad hoc. In particolare, applicherò questi selettori ad un problema classico: come alterare la visualizzazione delle voci di un menu di navigazione orizzontale quando ce ne sono troppe per andare bene nella modalità di layout iniziale, cioè, dimostrerò come passare da un layout display: table-cell a un layout display: inline-block quando il numero delle voci del menu diventa “maggiore o uguale a 6”.

Non farò affidamento su nessun JavaScript né su alcuna logica di template e il markup dell’elenco del menu rimarrà senza attribuzione di class. Utilizzando solo CSS, la tecnica onora il principio di separation of concerns secondo il quale il contenuto (HTML) e la presentazione (CSS) hanno ruoli chiaramente definiti. Il layout è affare del CSS e, ove possibile, solo del CSS.

Confronto tra il layout della barra del menu iniziale per meno di sei voci di menu con il layout che ne ha sei o più

La dimostrazione è disponibile su CodePen e vi farò riferimento per tutto l’articolo.

Per aiutarmi a illustrare questo requisito di quantità, utilizzerò in questo articolo dei grafici di calamari, che rappresenteranno gli elementi HTML. I calamari verdi con le spunte verdi rappresentano gli elementi che si abbinano al selettore CSS in questione, i calamari rossi con le croci sono gli elementi non selezionati e i calamari grigi denotano gli elementi che non esistono.

Una legenda per i tre simboli dei calamari che verranno usati nei seguenti diagrammi. Un calamaro verde (per gli elementi selezionati), un calamaro rosso (per gli elementi non selezionati) e un calamaro grigio per gli elementi che non esistono

Contare#section2

La chiave per determinare la quantità di elementi in un dato contesto è contarli. CSS non fornisce un’esplicita “API per i conteggi” ma possiamo risolvere lo stesso problema con una combinazione fantasiosa di selettori.

Contare fino a uno#section3

Il selettore :only-child fornisce un mezzo per assegnare stili agli elementi se appaiono isolati. Sostanzialmente, ci permette di “assegnare uno stile a tutti gli elementi figlio (child) di un particolare elemento, se contando questi figli (children) si ha 1 come totale”. A parte il suo compagno di scuderia :only-of-type, è l’unico selettore semplice che può essere descritto come “quantity-based”.

Nell’esempio seguente, uso :only-of-type per aggiungere uno stile speciale a qualunque pulsante che sia il solo elemento del suo tipo tra gli elementi sibling (fratelli). Dò a questi pulsanti solitari un font-size maggiore perché la singolarità suggerisce importanza.


button { 
  font-size: 1.25em;
}

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

Ecco la parte cruciale: se cominciassi con un pulsante a cui assegno una dimensione del font maggiore, e aggiungessi pulsanti prima o dopo questo, ciascun pulsante adotterebbe come conseguenza una dimensione del font più piccola. Lo stile di tutti gli elementi nell’insieme dipende da una soglia di quantità pari a due: se ci sono “meno di due” elementi, viene assegnato il font size più grande. Date un’occhiata ancora a tale codice con in mente la nozione “meno di due”:


button {
  font-size: 1.25em;
}

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

La logica meno di due significa che un elemento selezionato (calamaro verde) diventa due elementi non selezionati (calamari rossi) quando viene aggiunto un elemento

Se sembra più naturale, potete ribaltare la logica CSS usando la negazione e rendendo la condizione “più di uno”.


/* "More than one" results in a smaller font size */
button {
  font-size: 2em;
}

button:not(:only-of-type) {
  font-size: 1.25em;
}

La logica più di uno significa che un elemento non selezionato (calamaro rosso) diventa due elementi selezionati (calamari verdi) quando viene aggiunto un elemento.

Quantità#section4

Assegnare stili agli elementi basandosi sulle soglie “più di uno” e “meno di due” è un trucco interessante, ma un’interfaccia con “quantity query” flessibili dovrebbe accettare una qualunque quantità, ossia, dovrei poter assegnare uno stile per ogni valore di n “maggiore o uguale a n”. Allora potrei assegnare stili per valori “maggiori o uguali a 6” nel nostro menu di navigazione.

Con la prospettiva di raggiungere quest’ultimo obiettivo, cosa succederebbe se fossi in grado di assegnare stili a quantità discrete come “esattamente 6 in totale” o “esattamente 745”? Come potrei fare? Dovrei usare un selettore che mi permetta di attraversare insiemi di elementi di qualunque quantità nemerica.

Fortunatamente, il selettore :nth-last-child(n) accetta il numero “n”, mettendomi nelle condizioni di contare degli insiemi di elementi a partire dalla fine dell’insieme fino all’inizio. Per esempio, :nth-last-child(6) seleziona il sesto elemento a partire dall’ultimo tra gli elementi sibling.

Le cose si fanno interessanti quando concateno :nth-last-child(6) con :first-child, introducendo una seconda condizione. In questo caso, cerco qualsiasi elemento che sia contemporaneamente il sesto elemento dalla fine e il primo elemento.


li:nth-last-child(6):first-child {
  /* green squid styling */
}

Se questo elemento esiste, la quantità di elementi dell’insieme deve essere esattamente 6. In maniera lievemente radicale, ho scritto del CSS che mi dice quanti elementi sto guardando.

Dei sei calamari, il primo è verde e il resto sono rossi. Il primo è soggetto al selettore nth-last-child(6) così come al selettore first-child

Tutto quello che rimane da fare è sfruttare questo elemento chiave per assegnare gli stili agli elementi rimanenti nell’insieme. Per fare ciò, utilizzo il general sibling combinator.

Sei calamari verdi perché il primo calamaro verde è combinato con il general sibling combinator che rende verdi tutti i calamari rossi che vengono dopo

Se non avete familiarità con il general sibling combinator, il - li in li:nth-last-child(6):first-child ~ li significa “qualsiasi elemento li ci sia dopo li:nth-last-child(6):first-child”. Nell’esempio seguente, ciascuno degli elementi adotta il colore del font verde se ce ne sono esattamente sei in totale.


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

Maggiore o uguale a 6#section5

Avere come target una quantità discreta, che sia 6, 19 o 653, non è particolarmente utile perché è una situazione troppo specifica. Sarebbe altrettanto inutile utilizzare delle larghezze discrete piuttosto che min-width o maz-width nelle nostre @media queries:


@media screen and (width: 500px) {
  /* styles for exactly 500px wide viewports */
}

Nel menu di navigazione voglio davvero cambiare il layout ad una soglia: uno spartiacque di quantità. Voglio cambiare quando ci sono sei o più elementi, non quando ci sono esattamente sei elementi. Quando raggiungo la soglia, vorrei cambiare da un layout a tabella distribuita ad una configurazione più semplice, con inline-block che vadano a capo. Ancora più importante, vorrei conservare quella configurazione a cui sono arrivato man mano che il numero di elementi aumenta ulteriormente.

La domanda è: come si fa a costruire un selettore di questo tipo? È una questione di offset.

L’argomento n+6#section6

Un altro argomento aritmetico adottabile dal selettore :nth-child() prende la forma di “n + [integer]”. Per esempio, :nth-child(n+6) assegna stili a tutti gli elementi in un insieme partendo dal sesto.

Un insieme di calamari rossi che diventano verdi al sesto calamaro fino alla fine dell'insieme (che può essere di qualunque dimensione), contando verso l'alto.

Sebbene questo abbia di per sé possibili applicazioni, non è un metodo di selezione “quantity-aware” in quanto tale: non stiamo assegnando alcuno stile perché ci sono sei o più elementi in totale, stiamo solo assegnando stili a quelli che hanno un numerale più alto di cinque.

Per cominciare a risolvere il problema in maniera appropriata, quello che dobbiamo davvero fare è creare un insieme di elementi che esclude gli ultimi cinque oggetti. Utilizzando l’opposto di :nth-child(n+6):nth-last-child(n+6)— posso applicare le proprietà del layout cambiato a tutti gli “ultimi elementi” a partire dal sesto, contando all’indietro verso l’inizio dell’insieme.


li:nth-last-child(n+6) {
  /* properties here */
}

Questo esclude gli ultimi cinque oggetti da un insieme di qualunque lunghezza, il che implica che quando si riduce la lunghezza dell’insieme a meno di sei, si smettono di vedere gli oggetti selezionati. È una specie di effetto “sliding doors”.

Un insieme di calamari verdi (a sinistra) e calamari rossi (a destra) diventa un insieme solo di calamare rossi quando l'insieme ha meno di sei elementi

Se, effettivamente, l’insieme ha sei o più elementi in totale, allora tutto quello che rimane è assegnare gli stili anche a quegli ultimi cinque oggetti. Questo è facile: dove ci sono più di sei oggetti, devono esistere uno o più oggetti che “ritornano true” (in gergo JavaScript) per la condizione nth-last-child(n+6). Ciascuno di questi elementi esistenti può essere combinato con “~” per influenzare tutti gli oggetti (inclusi gli ultimi cinque) che lo seguono.

Quando vengono aggiunti dei calamari ad un insieme di calamari rossi, i calamari a destra dell'insieme diventano verdi e possono essere usati per rendere verde anche il resto dei calamari (con il general sibling combinator)

La soluzione sorprendentemente tersa del nostro problema è questa:


li:nth-last-child(n+6),
li:nth-last-child(n+6) ~ li {
  /* properties here */
}

Naturalmente, 6 può essere sostituito con un qualunque intero positivo, anche 653.279.

Minore o uguale a n#section7

Come nell’esempio di prima con :only-of-type, potete capovolgere la logica, passando da “maggiore o uguale a n” a “minore o uguale a n”. Il tipo di logica che usate dipende da quale stato considerate come lo stato di default più naturale. “Minore o uguale a n” è possibile negando n e ripristinando la condizione :first-child.


li:nth-last-child(-n+6):first-child,
li:nth-last-child(-n+6):first-child ~ li {
  /* properties here */
}

Effettivamente, l’uso di “-” cambia la direzione della selezione: invece di puntare all’inizio partendo dal sesto, punta verso la fine partendo dal sesto. In entrambe i casi, il selettore include il sesto oggetto.

nth-child versus nth-of-type#section8

Notate che nel precedente esempio ho usato :nth-child() e :nth-last-child(), non :nth-of-type() e :nth-last-of-type(). Dal momento che ho a che fare con elementi <li> e che gli <li> sono gli unici figli legittimi degli <ul>, :last-child() e :last-of-type() funzionerebbero entrambe in questo caso.

Le famiglie di selettori :nth-child() e :nth-of-type() hanno diversi vantaggi a seconda di quello che si sta cercando di ottenere. Poiché :nth-child() è “element agnostic“, potreste applicare la tecnica descritta per i sibling di diverso tipo:


<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) ~ * {
  /* properties here */
}

(Si noti il modo in cui sto usando qui il selettore universale per mantenere ”l’element agnosticism”. :last-child(n+3) ~ * vuol dire “qualunque elemento di qualunque tipo che segue :last-child(n+3)”.)

Il vantaggio di :nth-last-of-type(), d’altro canto, è che siete in grado di puntare gruppi di elementi uguali dove sono presenti altri sibling di diversi tipi. Per esempio, potreste puntare la quantità di paragrafi nella snippet seguente, indipendentemente che siano inclusi in un <div> e in un <blockquote>.


<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 {
  /* properties here */
}

Supporto per i selettori#section9

Tutti i selettori CSS2.1 e CSS3 utilizzati in questo articolo sono supportati da Internet Explorer 9 e successivi, inclusi tutti i browser ragionevolmente recenti per mobile/handheld.

Il supporto da parte di Internet Explorer 8 per la maggior parte dei tipi di selettore è buono, ma tecnicamente parziale, quindi dovreste considerare un polyfill JavaScript. In alternativa, potreste accoppiare i selettori per la strategia di layout “più sicura” con classi specifiche per IE9. Nel caso del menu di navigazione, l’opzione più sicura è quella che viene incontro alle esigenze di più voci, utilizzando inline-block. Il blocco della dichiarazione potrebbe essere più o meno così:


nav li:nth-last-child(n+6),
nav li:nth-last-child(n+6) ~ li, 

.lt-ie9 nav li {
  display: inline-block;
  /* etc */
}

Nel mondo reale#section10

Supponiamo che il nostro menu di navigazione appartenga a un sito con un content management system. A seconda di chi amministra il tema, sarà popolato con un numero maggiore o minore di opzioni. Alcuni autori faranno in modo che tutto risulti semplice fornendo solo i link “Home” e “About”, mentre altri riempiranno il menu di pagine personalizzate e di opzioni per categoria.

Fornendo layout alternativi a seconda del numero di voci di menu presenti, aumentiamo l’eleganza con la quale tolleriamo diverse implementazioni del tema: gestiamo il contenuto variabile come faremmo con le dimensioni variabili dello schermo.

Confronto tra il layout della barra del menu iniziale per meno di sei voci con il layout per sei o più voci

Ecco fatto: calamari ahoy! Potete adesso aggiungere al vostro repertorio la quantità come condizione per gli stili.

Design content-independent#section11

Il responsive web design risolve un problema importante: rende lo stesso contenuto digeribile comodamente tra diversi device. Che la gente riceva contenuto diverso solo perché ha un dispositivo diverso è inaccettabile. Analogamente, è inaccettabile che un design detti legge sulla natura del contenuto. Non possiamo dire a un editor: “Puoi tralasciarlo? Incasina il layout.”

Indipendentemente da quale forma assuma il contenuto, e da quanto ce ne sia in qualunque momento, è spesso indeterminato: un altro sconosciuto. E non possiamo sempre far affidamento sugli script di text wrapping e troncamento. Per avere davvero il polso dell’indipendenza del contenuto, dobbiamo sviluppare nuovi tools e tecniche. Le quantity queries sono solo una delle tante idee.

Il web design è cambiamento, differenza, incertezza. È non sapere. Nella sua unicità è una modalità di visual design che non si manifesta in una forma ma nell’anticipazione di varie forme che qualcosa potrebbe assumere. Per alcuni è insopportabilmente sconcertante, ma per voi e per me è una sfida che apprezziamo. Come l’elusivo calamaro gigante, è un cliente davvero sfuggente.

Illustrazioni: {carlok}

Nessun commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Altro da ALA

Webwaste

In questo estratto da World Wide Waste, Gerry McGovern esamina l'impatto ambientale di siti web pieni zeppi di asset inutili. Digital is physical. Sembra economico e gratis ma non lo è: ci costa la Terra.
Industry