No. 200

Titolo originale: Using CSS Mod Queries with Range Selectors

Pubblicato in: CSS

Scritto da Patrick Clancey

Recentemente, mi è stato chiesto di costruire una semplice lista che venisse visualizzata in una griglia, che sarebbe cominciata con un singolo elemento e sarebbe cresciuta durante il giorno, pur rimanendo ordinata, indipendentemente dalla sua lunghezza. Quindi, come si fa a volte quando si è indaffarati con una cosa e ci viene chiesto di farne una completamente diversa, ho cercato di pensare ad una qualsiasi ragione per cui non si potesse fare, non ne ho trovate e ho distrattamente risposto “Sì”.

All'epoca, lavoravo al sito web di un'organizzazione che si occupa di notizie con sede a Londra. Avevamo passato l'anno precedente a migrare il loro CMS alla piattaforma Adobe AEM e al contempo stavamo implementando una UI responsive, entrambe grandi miglioramenti. Dal momento che quella fase era completa, stavamo cominciando a concentrarci sui dettagli della UI e sulla realizzazione di nuove feature. Il progetto di sviluppo era diviso tra un numero di piccoli team semi-autonomi. Il mio team si stava concentrando sulle pagine hub e io guidavo il lavoro sulla UI.

Sostanzialmente, ogni pagina hub è una lista di liste, messe lì semplicemente per aiutare i lettori a trovare dei contenuti che possano interessarli. Come potete immaginare, un sito web di news è quasi esclusivamente fatto di liste di contenuto! Una pagina piena di liste verticali generiche non sarebbe di aiuto né tantomeno gradevole: volevamo che ai lettori piacesse scorrere il contenuto collegato alla loro sfera di interesse. Le sezioni dovevano essere distinte e le liste avrebbero dovuto essere sia distinguibili individualmente sia formare un tutto armonioso. In breve, la visualizzazione era cruciale per l'usabilità e l'efficacia dell'intera pagina.

Quella “semplice lista” che ho detto che avrei creato sarebbe stata di alto profilo, posizionata nel suo pannello vicino al top della pagina hub, con lo scopo di sottolineare uno specifico punto di interesse. Cominciando con un item e crescendo durante tutto il giorno man mano che vengono pubblicati nuovi articoli, la lista doveva essere una griglia rettangolare piuttosto che una singola colonna e non aver mai degli item che avanzano nell'ultima riga. E non importa quanti elementi figlio contenga in un qualunque momento, deve rimanere ordinata e pulita perché dovrebbe apparire prima del fold. Ogni item dovrebbe essere più o meno quadrato, con un primo item con larghezza impostata al 100%, i secondi due al 50% e tutti i seguenti item al 33%, sistemati in righe di tre. Improvvisamente, la mia semplice lista è tutt'altro che semplice.

Non tutti vogliono una griglia generica o uno stack di item identici: c'è qualcosa di bello nel selezionare gli elementi prominenti, nel raggrupparli e nel terminare con grazia i fine riga. Questi stili possono essere “hard coded” se sapete che la lista sarà sempre di una lunghezza esatta, ma diventa più difficile quando la lunghezza può cambiare. In che modo posso garantire che quell'ultima riga rimanga in ordine quando ci sono meno di tre item?

Varie sistemazioni dei list item che distruggono o meno il layout pianificato nella riga in fondo. Il nostro layout pianificato si distruggerebbe visivamente man mano che si aggiungono item alla lista.

Quando si è trattato di costruire effettivamente questa cosa, ho realizzato che conoscere la lunghezza della lista non era molto utile. Siccome avevo adorato l'eccellente articolo di Heydon Pickering sulle quantity queries per CSS, ho supposto di poter scoprire la lunghezza della lista usando le QQ, poi di assegnarle uno stile di conseguenza e che tutto sarebbe andato bene.

Ma dal momento che la mia lista avrebbe potuto essere di una qualsiasi lunghezza, avevo bisogno di un numero infinito di QQ per soddisfare le richieste! Non potevo fare una QQ per ogni eventualità. Inoltre, giravano voci che prima o poi si sarebbe aggiunto un pulsante “Load More”, permettendo agli utenti di aggiungere dinamicamente un'altra decina di item. Avevo bisogno di una soluzione diversa.

Dopo un piccolo crollo nervoso, mi sono chiesto: Cosa farebbe Lea Verou? Beh, non andare in panico sarebbe un buon inizio. Inoltre, potrebbe essere utile semplificare e identificare i requisiti sottostanti. Dal momento che la lista sarebbe fondamentalmente composta da righe di tre elementi, avevo bisogno di sapere il resto della divisione mod 3.

La “mod” query

Essere in grado di selezionare e assegnare stili agli elementi in base al numero di fratelli è ottimo, ma c'è di più che la semplice lunghezza. In questo caso, sarebbe molto meglio sapere se la mia lista sia divisibile per un certo numero piuttosto che sapere quanto è lunga.

Sfortunatamente, non c'è una mod query nativa in CSS, ma possiamo crearne una combinando due selettori: :nth-child(3n) (aka il selettore “modulo”) e il selettore :first-child.

La query seguente seleziona tutto se la lista è divisibile per tre:

li:nth-last-child(3n):first-child,
li:nth-last-child(3n):first-child ~ li { 
 … selects everything in a list divisible by three … 
}
Quattro righe di list items (gatti nelle scatole). Le righe top e bottom sono selezionate (colore pieno, non ingrigite) perché ciascuna è divisibile per 3. Sono selezionate solo le righe divisibili per 3. Vedi il Pen Using CSS Mod Queries with Range Selectors: Fig 2 di Patrick (@clanceyp) su CodePen. Immagine del gatto di Paper Bird Publishing.

Esaminiamo quel codice. (Negli esempi, uso “li“ per “list item”).

Il selettore css:

li:nth-last-child(3n):first-child ~ li

Seleziona tutti i sibling seguenti:

... ~ li

Il primo figlio (il primo “li“ nell'elenco, in questo caso):

...:first-child ...

Ogni terzo item partendo dalla fine della lista:

...:nth-last-child(3n):...

Questa combinazione in sostanza significa che se il primo figlio è 3n dalla fine, allora selezioni tutti i suoi sibling.

La query seleziona tutti i sibling del primo item, ma non include il primo item stesso, perciò dobbiamo aggiungere un selettore separatamente per questo.

li:nth-last-child(3n):first-child,
li:nth-last-child(3n):first-child ~ li { 
… styles for list items in a list divisible by 3 …  
}

Guardate la demo e provatela!

Cosa succede coi resti?

Con la mia mod query, posso selezionare tutti gli item in un elenco se l'elenco è divisibile per tre, ma dovrò applicare stili diversi se ci sono dei resti. (Nel caso del reso 1, avrò solo bisogno di contare alla rovescia nel CSS dal penultimo elemento invece che dall'ultimo. Posso fare ciò semplicemente aggiungendo +1 alla query.)

li:nth-last-child(3n+1):first-child,
li:nth-last-child(3n+1):first-child ~ li { 
… styles for elements in list length, mod 3 remainder = 1 …  
}

Lo stesso vale per il resto 2: devo solo aggiungere +2 alla query.

li:nth-last-child(3n+2):first-child,
li:nth-last-child(3n+2):first-child ~ li { 
… styles for elements in list length, mod 3 remainder = 2 …  
} 

Creare un selettore di intervallo (range)

Adesso ho un modo per determinare se la lunghezza della lista è divisibile per un qualsiasi numero, con o senza resto, ma ho ancora bisogno di selezionare un range. Così come per le mod query, non c'è in CSS un selettore di range nativo, ma possiamo crearne uno combinando due selettori: :nth-child(n) (i.e., “tutto ciò che sta sopra”) e :nth-child(-n) (i.e., “tutto ciò che sta sotto”).

Questo ci permette di selezionare gli item dal 3 al 5, inclusi:

li:nth-child(n+3):nth-child(-n+5){ 
... styles for items 3 to 5 inclusive ...
}
Una riga di sei list item (grafica dei gatti nelle scatole). I due a sinistra sono in scala di grigi, seguiti da tre gatti “selezionati” a colori e quello sulla destra è grigio. Abbiamo selezionato un range: i gatti 3, 4 e 5.

Vero, si potrebbe ottenere lo stesso risultato in maniera altrettanto semplice con la sintassi :nth-child(n) e puntando direttamente alle posizioni dell'item - li:nth-child(3), li:nth-child(4), li:nth-child(5){ ... } - ma definire l'inizio e la fine di un range è ovviamente molto più versatile. Scomponiamo brevemente il selettore per vedere come funziona.

Seleziona tutti gli item fino al quinto item incluso:

li:nth-child(n+3):nth-child(-n+5){}

Seleziona tutti gli item dal terzo in poi:

li:nth-child(n+3):nth-child(-n+5){}

Combinando i due — li:nth-child(n+3):nth-child(-n+5) — si crea il selettore di range.

Se osserviamo un esempio, potremmo avere una griglia di prodotti in cui i list item contengono un'immagine, il titolo e la descrizione. Supponiamo che l'immagine del prodotto parli da sola, così nella prima riga promuoviamo l'immagine e nascondiamo tutto il testo. Nella seconda e terza riga, mostriamo il titolo e l'immagine come una thumbnail, mentre nelle righe seguenti nascondiamo l'immagine e mostriamo il titolo e la descrizione su una singola riga.

Una griglia con la grafica di tre gatti nella riga superiore, poi due righe di blocchi comprendenti ciascuno una grafica di un gatto e un titolo di prodotto, poi quattro righe, ciascuna elencante il testo del titolo del prodotto e i dettagli del prodotto. Una griglia di prodotti per i nostri gatti. Abbiamo una grafica isolata nella riga superiore, piccole grafiche più i titoli prodotto nella seconda e nella terza riga e poi perdiamo le grafiche e mostriamo solo il testo per le righe seguenti. Vedi il Pen Using CSS Mod Queries with Range Selectors: Fig 4 di Patrick (@clanceyp) su CodePen.

Usando il range selector, possiamo selezionare i primi tre, dal quarto al nono e dal decimo in poi. Questo ci permette di cambiare i range nei vari breakpoint nel CSS così la nostra griglia prodotto rimane bellina e responsive.

Note sui mixin SCSS

Dal momento che stavo usando un preprocessore CSS, ho semplificato il mio codice usando le funzioni di questo: ecco i mixin SCSS per la creazione di range selector e mod query.

// range selector mixin
@mixin select-range($start, $end){
  &:nth-child(n+#{$start}):nth-child(-n+#{$end}){
   @content;
   }
}
// mod query mixin
@mixin mod-list($mod, $remainder){
  &:nth-last-child(#{$mod}n+#{$remainder}):first-child,
  &:nth-last-child(#{$mod}n+#{$remainder}):first-child ~ li {
    @content;
    }
}

Poi posso annidare i mixin nel mio codice.

li {
@include mod-list(3, 0){
  @include select-range(3, 5){
    // styles for items 3 to 5 in a list mod 3 remainder = 0
    }
  }
}

Il che, se non altro, è molto più semplice da leggere!

Unire tutti i pezzi

Così, adesso che ho un piccolo arsenale di tool che mi aiutano a gestire i mod, i range e i range all'interno dei mod, posso staccarmi dall'implementazione standard di liste a lunghezza fissa o a layout fisso. L'uso creativo delle mod queries e dei selettori di range mi permette di applicare stili per cambiare il layout degli elementi.

Tornando ai requisiti originali (far sì che la mia lista si comporti bene) è stato subito chiaro che se avessi assegnato degli stili alla lista assumendo che fosse un multiplo di tre, allora ci sarebbero stati solo altri due use case da supportare:

  • Mod 3, resto 1
  • Mod 3, resto 2

Se c'era un item rimanente, avrei fatto prendere tre item alla seconda riga (invece che i due di default), ma se il resto fosse stato 2, avrei fatto prendere due item alla terza riga (con il quarto e il quinto item al 50%).

Alla fine, non mi servivano numerose query e le uniche di cui avevo bisogno in realtà erano piuttosto semplici.

C'era un caso speciale: cosa succede se la lista contiene solo due elementi?

Ho risolto questo caso con una query per selezionare il secondo item quando è anche l'ultimo figlio.

li:nth-child(2):last-child { 
... styles for the last item if it’s also the second item ...
}

In fin dei conti, le queries non erano così difficili come mi aspettavo: avevo solo bisogno di combinare i selettori mod e range.

li:nth-last-child(3n):first-child /* mod query */ 
~ li:nth-child(n+3):nth-child(-n+5){ /* range selector */
... styles for 3rd to 5th elements, in a list divisible by 3 ...
}

Alla fine, il mio CSS aveva più o meno questo aspetto:

/* 
  default settings for list (when its mod 3 remainder 0)
  list items are 33% wide
  except; the first item is 100% 
          the second and third are 50%
*/
li {
  width: 33.33%;
}
li:first-child {
  width: 100%;
}
/* range selector for 2nd and 3rd */
li:nth-child(n+2):nth-child(-n+3){
  width: 50%;
}
/* overrides */
/* mod query override, check for mod 3 remainder = 1 */  
li:nth-last-child(3n+1):first-child ~ li:nth-child(n+2):nth-child(-n+3) {
  width: 33.33%; /* override default 50% width for 2nd and 3rd items */
}
/* mod query override, check for mod 3 remainder = 2 */ 
li:nth-last-child(3n+2):first-child ~ li:nth-child(n+4):nth-child(-n+5) {
  width: 50%; /* override default 33% width for 4th and 5th items */
}
/* special case, list contains only two items */
li:nth-child(2):last-child {
  margin-left: 25%;
}

Sperimentate da soli (e una nota sul supporto dei browser)

Le mod queries e i selettori di range usati in questo articolo si basano sui selettori CSS3, quindi funzioneranno in tutti i browser moderni che supportano CSS3, incluso Internet Explorer dal 9 in poi (ma ricordate, IE si aspetta una doctype valida).

Ho creato un piccolo mod query generator che potete usare per sperimentare con le mod queries.

Quando mi sono imbattuto per la prima volta nelle QQ ho pensato che fossero grandiose ed interessanti ma perlopiù teoriche, senza molti use-case pratici nel mondo reale. Tuttavia, con l'uso da mobile che ha battuto quello da desktop e con il responsive design che è ormai la norma, il bisogno di mostrare liste, puntare a parti di liste a seconda della lunghezza/mod e mostrare liste in maniera diversa a diversi breakpoint è diventato molto più comune. Tutto questo porta davvero all'applicazione pratica delle QQ e sto scoprendo sempre di più che esse sono una parte essenziale del toolkit dello sviluppatore di UI.

Share/Save/Bookmark
 

Discutiamone

Ti sembra interessante? Scrivi tu il primo commento


Cenni sull'autore

Patrick Clancey

Patrick Clancey costruisce siti web professionalmente da più di 16 anni. È appassionato di tutto quello che riguarda lo sviluppo di user interface. Attualmente, lavora come Adobe AEM implementation engineer in Cognifide. Potete seguire i suoi progetti open-source su Github e le sue estensioni per Chrome nel Chrome webstore.

Questo sito per poter funzionare utilizza i cookie. Per saperne di più visita la pagina relativa all' INFORMATIVA