Assegnare uno stile ai numeratori delle liste ordinate non è un'operazione immediata, in quanto lo standerd CSS non offre al momento un selettore che permetta di isolare i numeri che contrassegnano gli elementi delle liste (markers).
Eppure come abbiamo già visto in un altro articolo è possibile ridisegnare i numeratori grazie ad una funzonalità presente nella specifica di CSS 2.1: i contatori.
I contatori CSS
Si tratta di variabili i cui valori possono essere incrementati grazie a dichiarazioni CSS che permettono di tenere in memoria il numero di volte in cui il contatore stesso è utilizzato all'interno del DOM. Per gestire i contatori, la specifica fornisce le proprietà:
- counter-reset, dichiara il nome del contatore;
- counter-increment, incrementa il valore di una unità.
Le due proprietà possono essere assegnate a qualunque elemento del DOM, e quindi i valori dei contatori possono essere modificati arbitrariamente.
Il valore del contatore viene mandato a video grazie alle funzioni counter()
e counters()
della proprietà content
.
I contatori e le liste ordinate
Prendiamo come esempio una semplice lista ordinata:
<ol class="number">
<li>Lorem ipsum dolor sit amet, ... </li>
<li>Lorem ipsum dolor sit amet, ... </li>
</ol>
Assegniamo, quindi, all'elemento ol il seguente stile:
ol.number {
width: 32em;
margin: 0;
padding: 0;
margin-left: 4em;
list-style-type: none;
counter-reset: simple-counter;
}
Con la proprietà list-style-type
viene annullato lo stile della lista, mentre con counter-reset
viene impostato il contatore simple-counter
. Il valore predefinito del contatore è 0, ma counter-reset
permette anche di assegnare un valore iniziale diverso:
ol.number {
...
list-style-type: none;
counter-reset: simple-counter 4;
}
In questo modo, il primo elemento della lista sarà contrassegnato dal numero 5. Una volta impostato il contatore, bisogna mandare a video i valori che questo assume man mano che viene aggiornato.
Per raggiungere lo scopo, si ricorre alla proprietà content dello pseudo-elemento ::before, come nella dichiarazione che segue:
ol.number li::before {
position: absolute;
top: 0;
left: -0.8em;
font-size: 2.8em;
line-height: 1em;
content: counter(simple-counter);
counter-increment: simple-counter;
}
La funzione counter()
restituisce il valore del contatore, che quindi può essere mandato a video con la proprietà content
.
Il codice CSS completo e l'esempio dal vivo.
Le funzioni counter() e counters()
Grazie alla proprietà content, assegnata agli pseudo-elementi ::before
e ::after
, è possibile generare un contenuto all'interno di un elemento del DOM. Tra i possibili valori della proprietà content, le funzioni counter() e counters() forniscono i valori correnti dei contatori.
Counter
La funzione counter() accetta uno o due argomenti:
Argomento | Descrizione |
---|---|
name | il nome del contatore |
style | lo stile della formattazione. Il valore predefinito è decimal , ma è possibile impostare uno qualunque dei valori della proprietà list-style-type |
Così, nell'esempio precedente, la dichiarazione avrebbe potuto essere anche la seguente:
ol.number li::before {
...
content: counter(simple-counter, upper-roman);
counter-increment: simple-counter;
}
Il nome del contatore non deve necessariamente essere univoco. Possono esistere, infatti, più contatori con lo stesso nome, ognuno valido nell'ambito di uno specifico elemento (scope). La funzione counters()
permette di visualizzare i valori di tutti i contatori aventi lo stesso nome, dal più esterno al più interno, separandoli con una specifica stringa. Ciò consente, ad esempio, di generare una numerazione alternativa per le liste annidate. Si consideri, ad esempio, la seguente struttura:
<ol class="toc">
<li>Core JavaScript <!-- Part 1 -->
<ol>
<li>Lexical Structure <!-- Chapter -->
<ol>
<li>Character Set</li> <!-- Paragraph -->
<li>Comments</li>
<li>Literals</li>
<li>Identifiers and Reserved Words</li>
<li>Optionals Semicolons</li>
</ol>
</li>
...
</ol>
Il markup qui sopra genera tre livelli di liste ordinate. Il primo livello individua le parti, il secondo individua i capitoli, il terzo, infine, i paragrafi di un libro. Vediamo, quindi, le dichiarazioni CSS. Come nell'esempio precedente, va impostato il contatore e annullati gli stili predefiniti delle liste:
.toc,
.toc ol {
counter-reset: listitem;
list-style-type: none;
}
Trattandosi di più liste annidate, abbiamo selezionato sia la lista principale, sia tutte le liste ordinate da essa discendenti (.toc ol
). Subito dopo visualizziamo i valori dei contatori:
.toc li:before {
counter-increment: listitem;
content: counters(listitem, ".")" ";
}
Prima di ogni elemento di lista viene aggiunto lo pseudo-elemento ::before
, in corrispondenza del quale viene incrementato il contatore listitem
; quindi, al suo interno, viene inserito il contenuto generato dal valore della proprietà content
. In questo caso, la funzione counters()
fornisce i valori di tutti i contatori il cui nome è listitem
, a cominciare dal contatore della lista più esterna, separati da un punto; dopo il contatore, viene inserito uno spazio.
Potrebbe essere opportuno dare maggiore enfasi alla prima lista della gerarchia, magari evidenziando gli elementi che individuano una parte principale del libro. Per far questo, bisogna sovrascrivere il blocco precedente con un selettore specifico per gli elementi di lista direttamente discendenti dalla lista principale:
.toc > li::before {
content: "Part " counter(listitem)". ";
}
Si noterà subito che abbiamo utilizzato la funzione counter()
e non la funzione counters()
. Ciò in quanto al primo livello non sarà necessario separare i valori dei tre contatori. Al numero progressivo viene premessa la stringa "Part "
e aggiunta la stringa ". "
.
In questo esempio bisogna stare attenti alla specificità dei selettori, che è esattamente la stessa. Per questo motivo è necessario che i blocchi di dichiarazioni vengano inseriti nello stesso ordine del nostro esempio.
Manca pochissimo. Per distinguere meglio gli elementi delle liste annidate, assegniamo colori e dimensioni diverse agli elementi di lista, in base al livello gerarchico:
.toc li {
line-height: 1.4em;
color: #000;
font-size: .82em;
}
.toc > li {
color: red;
font-size: 2em;
}
.toc > li > ol > li {
color: blue;
}
Anche in questo caso, la specificità dei selettori è la stessa, quindi l'ordine dei blocchi di dichiarazioni è determinante per la corretta visualizzazione degli elementi.
Tre liste ordinate annidate con i rispettivi contatori
Il codice CSS completo e l'esempio dal vivo.
Soluzioni grafiche
Una volta conosciuti gli strumenti a disposizione, è possibile sperimentare alcune soluzioni grafiche. Partiamo dalla semplice lista ordinata dei primi esempi:
<ol class="transformed">
<li>Lorem ipsum dolor sit amet, ... </li>
<li>Lorem ipsum dolor sit amet, ... </li>
</ol>
I primi blocchi di dichiarazioni dovrebbero essere ora ben comprensibili:
ol.transformed {
width: 32em;
margin: 0;
padding: 0;
margin-left: 4em;
list-style-type: none;
counter-reset: my-counter 8;
}
ol.transformed li {
position: relative;
margin-bottom: 2em;
padding-left: .4em;
line-height: 1.2em;
min-height: 3em;
border-left: 2px solid #ccc;
background-color: rgba(255, 255, 255, .8);
}
Il codice si conclude con l'assegnazione degli stili allo pseudo-elemento ::before:
ol.transformed li::before {
position: absolute;
top: 0;
right: 10.8em;
min-width: 1.2em;
min-height: 1em;
padding: .1em 0;
font-size: 3em;
line-height: 1em;
text-align: center;
content: counter(my-counter);
counter-increment: my-counter;
transform: rotate(-30deg) translate(.2em, .3em);
overflow: hidden;
z-index: -999;
color: #999;
background-color: #000;
}
Abbiamo impostato la distanza del marker a 10.8em dal bordo destro dell'elemento superiore, ossia l'elemento di lista. La misura è derivata dal seguente calcolo:
32em (larghezza della lista ol)
0.4em (padding-left degli elementi della lista)
3em (dimensioni del font dello pseudo-elemento li::before)
Distanza dal margine destro: ( 32 + 0.4 ) ÷ 3 = 10.8em
Grazie alla proprietà transform
, i marker vengono ruotati e traslati sui due assi; infine viene assegnato uno sfondo nero. Il risultato è quello mostrato nella figura che segue.
Il codice CSS e l'esempio dal vivo
Cerchi
Con poche modifiche diventa possibile dare ai markers un aspetto del tutto inedito. Riprendiamo l'esempio precedente e modifichiamo solo le dichiarazioni relative allo pseudo-elemento ::before
:
ol.transformed li::before {
position: absolute;
top: 0;
right: 10.8em;
min-width: 1.2em;
min-height: 1.2em;
padding: 0;
font-size: 3em;
line-height: 1.2em;
text-align: center;
content: counter(my-counter);
counter-increment: my-counter;
color: #999;
background-color: #000;
border: 4px solid #999;
border-radius: 50%;
}
Rispetto all'esempio precedente abbiamo modificato l'altezza minima dell'elemento, azzerato il padding e alzato a 1.2 l'altezza delle linea del testo. Abbiamo eliminato le proprietà transform
, overflow
e z-index
e aggiunto bordi arrotondati al 50% delle dimensioni dei lati, di 4px di spessore.
L'effetto in Safari è illustrato nella figura che segue.
Il file CSS e l'esempio dal vivo
Animazioni
Più sopra abbiamo assegnato la proprietà transform
allo pseudo-elemento li::before
. Allo stesso modo è possibile assegnare la proprietà transition per generare un'animazione sul marker dell'elemento.
Lasciamo ancora una volta invariati gli stili della lista e degli elementi li
, e modifichiamo gli stili dei marker:
ol.transformed li::before {
position: absolute;
top: 0;
right: 10.8em;
min-width: 1.2em;
min-height: 1em;
padding: .1em 0;
font-size: 3em;
line-height: 1em;
text-align: center;
content: counter(my-counter);
counter-increment: my-counter;
transform: rotate(-30deg) translate(.2em, .3em);
overflow: hidden;
z-index: -999;
color: #999;
background-color: #000;
transition: all .2s ease-out;
}
ol.transformed li:hover::before {
transform: rotate(30deg);
}
Al passaggio del mouse sull'elemento li
, verrà applicata una rotazione di 30 gradi in senso orario. La proprietà transition
, assegnata allo pseudo-elemento nel suo stato iniziale, animerà la rotazione in modo che il marker sembrerà girare su se stesso.
Al passaggio del mouse sul list item, il marker si anima ruotando su se stesso di 60°
Il codice CSS completo e l'esempio dal vivo
Conclusioni e riferimenti
Contenuto generato, contatori e pseudo-elementi sono gli ingredienti che permettono di aggirare una lacuna del Cascading Style Sheet, che al momento non permette di assegnare direttamente stili ai contatori delle liste.
Eppure in previsione c'è l'introduzione dello pseudo-elemento ::marker
, che dovrebbe in futuro risolvere il problema. Purtroppo, al momento nessun browser ne offre il supporto.
Gli esempi presentati sono stati testati sulle versioni correnti di Firefox, Chrome, Opera e Safari.
Nella stesura di questo articolo si è fatto riferimento alle seguenti risorse:
- W3C Recommendation: Generated Content - Automatic Counters and Numbering
- W3C Draft: CSS Lists and Counters Module Level 3
- MDN: content
- MDN: Using CSS counters
- MDN: counter-reset
- MDN: counter-increment
- Caniuse: CSS counters