Očíslovaný seznam v CSS

Už vás někdy napadlo, jak udělat např. číslování kapitol v dokumentu bez toho, abyste měli celý dokument uzavřený v jednom velkém OL-LI seznamu? A určitě jste pak jen mávli rukou: „To udělám přes jQuery.“ Ale víte, že to jde bez skriptu, jen pomocí CSS?

To je nějaká novinka?

CSS vlastnost counter je již definována v CSS 2.1 a nedá se tedy říci, že by šlo o nějakou novinku. A to je možná důvod, proč se o ní neví, protože teprve s CSS 3 přišla různá formátovací kouzla a vychytávky a vývojáři se teprve začali více zajímat o všechny „nové“ možnosti (a narážet na to, kde všude nefungují). Naproti tomu je counter podporován ve všech prohlížečích včetně starých Explorerů.

Počátky

Běžně si pro očíslované či neočíslované seznamy (resp. z anglického ordered/unordered lists správně řazené a neřazené seznamy) vystačíte s HTML:

Očíslovaný seznam získáte pomocí:
<ol>
    <li>HTML tagů
    <li>CSS listů
    <li>CSS counterů
</ol>

A pokud nechcete používat LI, stačí změnit CSS:

.list {
    display: list-item;
    list-style-type: decimal;
}

Nevýhoda těchto způsobů je v tom, že nemáte příliš kontrolu nad tím, jak se budou prvky číslovat a pokud se seznam jakkoliv přeruší, začne opět od jedničky.

A právě kvůli tomu existuje counter:

.list-start {
    counter-reset: seznam 0;
}
.list-item:before {
    counter-increment: seznam 1;
    content: counter(seznam) '.'
}

Uvedený kód provádí následující:

  • Nejprve na začátku seznamu nastaví počet na nulu.
  • Pak, před tím, než vykreslí položku seznamu, zvedne počet o 1.
  • A nakonec vypíše počet a za ním tečku (např.: 1., 2., 3., atd.)

Poznámka: Česky se counter překládá jako čítač, ale dá se použít i označení počítadlo. V textu budu používat anglický výraz, protože tím myslím přímo danou CSS vlastnost.

Použít se to dá například pro číslování kapitol a podkapitol:

body { counter-reset: kapitola }
h1:before {
    counter-increment: kapitola;
    content: count(kapitola);
    counter-reset: podkapitola;
}
h2: before {
    counter-increment: podkapitola;
    content:
      counter(kapitola) '.' counter(podkapitola);
}

Vygenerované nadpisy pak mohou vypadat:

1. úvod
2. stať
2.1 Slovo Úvodem
2.2 Vo co tu de?
2.3 Na závěr
3. Závěr

 Syntaxe

CSS counter se skládá ze dvou vlastností a dvou funkcí, které můžete použít ve vlastnosti content (která vkládá do dokumentu text).

V příkladech budu používat smyšlenou hru, která prvkům na stránce přiřazuje CSS třídy a prohlížeč pak automaticky počítá body pomocí CSS counterů.

counter-increment

Nejdůležitější je vlastnost counter-increment, pomocí které počítáte prvky. Funguje tak, že pokaždé, když prohlížeč vykresluje element mající tuto vlastnost, zvýší daný counter o zadanou hodnotu:

.get-1-point { counter-increment: points; }
.get-2-points { counter-increment: points 2; }
.lose-3-points { counter-increment: points -3; }

Pomocí jednoho prvku lze zvýšit i více counterů jednoduše tím, že je uvedete za sebe. Může zadat i počet, přičemž countery a inkrementy se nijak neoddělují (vyjma mezery), prohlížeč vyhodnotí zápis jen na základě toho, že counter je jméno (A-Z) a inkrement je číslo:

.get-1-point { 
    counter-increment: points collected;
}
.get-2-points {
    counter-increment: points 2 collected;
}
.lose-3-points {
    counter-increment: points -3 collected -1;
}
.get-5-points {
    counter-increment: points 2 points 3; 
} /* uvedení stejného counteru vícekrát
     započte všechna uvedená zvýšení */

Pozor na to, že stejně jako všechny ostatní CSS vlastnosti se i counter přepisuje (správně kaskáduje z názvu Cascade Style Sheets) – pokud tedy jednomu prvku definujete více counter-increment vlastností, bude se brát v úvahu pouze ta poslední:

.get-1-point {
    counter-increment: points collected;
    counter-increment: points 2;
} /* zvýší points o 2,
      ale collected nezvýší vůbec!!! */
/* -------------------------- */
.get-2-points {
    counter-increment: points 2;
}
.get-2-points.double { 
    counter-increment: points 4; 
}
.get-2-points.dummy {
    counter-increment: points 0;
} /* Double zvýší o 4, a Dummy nezvýší o nic */
/* -------------------------- */
li { counter-increment: odrazky; }
ul * { counter-increment: odrazky 0; }
/* nikdy nezvýší counter odrazky, protože vždy
   vyhraje styl 'ul *' */

counter-reset

Pomocná vlastnost je counter-reset, pomocí které můžete nastavit výchozí hodnotu counteru nebo ho nastavit na pevně dané číslo.

I když prohlížeče dosud nepoužitý counter považují za nulový, CSS specifikace i programátorská doporučení říkají, že „každý čítač by se měl před použitím nastavit na 0„. Pokud k tomu nemáte žádné specifické místo, je dobré použít BODY, které je v každém dokumentu jen jednou a vykresluje se jako první:

body {
    counter-reset: points;
}

Lze samozřejmě použít i nějaké výchozí hodnoty:

.bonus-game { counter-reset: points 100; }

Zresetovat můžete i více counterů stejně jako je inkrementujete – zde platí i všechna pravidla a upozornění o překrývání pravidel apod.:

.bonus-game {
    counter-reset: points 100 collected; 
}
.hard-game {
    counter-reset: points -100 collected -25;
}

counter()

Počítání je hezké, ale pokud nevidíte výsledek, tak je to k ničemu. K tomu slouží funkce counter(), kterou můžete použít ve vlastnosti content:

.debug .game-item:before {
    content: counter(points); 
}
.results-points:after {
    content: counter(points) ' points';
}
.results-text:after {
    content: 'Your ' counter(collected) ' items earned you ' counter(points) ' points';
}

V content můžete používat i více funkcí nebo řetězců. Ty se oddělují pomocí mezery (nepoužívejte žádné „+“ nebo „.“ známé z JS nebo PHP). Nezapomínejte, že oddělovací mezery se nepočítají, takže mezery ve výsledném textu musíte vkládat do uvozovek.

Formát

Pomocí funkce counter() můžete měnit i formát vypisovaných čísel stejně jako můžete měnit běžný očíslovaný seznam.

.last-letter:after {
    content: counter(letters, upper-latin);
}

Použít lze všechny formáty: čísla (decimal) včetně úvodní nuly (decimal-leading-zero), římské číslice (lower-roman, upper-roman), písmena (lower-latin, upper-latin) či řecká písmena (lower-greek). Použít lze dokonce i odrážky (disc, circle, square), ale ty se vždy zobrazí pouze jako jeden znak (i když counter je 0), takže jejich použití je sporadické – jedině snad pro zobrazení odrážky u prvku, který není display:list-item:

a { display: block; }
a:before { content: counter(null, 'disc'); }

Vnořování

Jak jsem uvedl na začátku, jedno z nejužitečnějších použití je číslování nadpisů, kde přiřadíte styly jednotlivým elementům H1, H2, atd.

Co když ale potřebujete číslovat prvky, které nemají takhle oddělenou strukturu a je potřeba použít pro všechny stejný tag (např. LI, DIV, A, apod.)? Určitě vás napadne udělat si CSS třídy, které budete prvkům přiřazovat:

.level-0 { counter-reset: level-1; }
.level-1:before {
    counter-reset: level-2;
    counter-increment: level-1;
    content: counter(level-1);
}
.level-2:before {
    counter-reset: level-3;
    counter-increment: level-2;
    content:
           counter(level-1) '.' counter(level-2);
}
/* atd. */

Tím ale trochu popíráte onu automatičnost počítání a navíc se zbavujete možnosti nekonečného vnořování (vždy jste omezeni počtem úrovní, které napíšete do stylu).

CSS k tomu má samozřejmě nástroj, který funguje automaticky. Když totiž nastavíte vlastnost counter-reset na nějaký prvek, který se může vnořovat sám do sebe (např. <div><div></div></div>), prohlížeč při vnoření automaticky vytvoří nový counter (se stejným jménem), který bude používat ve vnořené úrovni (a s uzavíracím tagem daného prvku ho zase smaže a vrátí se k předchozímu):

div { counter-reset: spans; }
span { display:block; counter-increment: spans; }
span:before { content: counter(spans) '. '; }
div div span { padding-left: 2em; }

<div>
    <span><span>
    <div><span><span></div>
    <span>
</div>

/* Vygeneruje: */
1.
2.
    1.
    2.
3.

„To je šikovné, ale jak vypsat čísla všech úrovní, které jsme používali u nadpisů“, říkáte si? K tomu slouží funkce counters() (všimněte si „S“ na konci), která funguje jako funkce join() nebo implode() z JS či  PHP a automaticky spojí všechny úrovně daného counteru:

div { counter-reset: spans; }
span { display:block; counter-increment: spans; }
span:before { content: counters(spans,'.') ' '; }
 /* tady je ten rozdíl  ------^ a ---^  */
div div span { padding-left: 2em; }

<div>
    <span><span>
    <div><span><span></div>
    <span>
</div>

/* Vygeneruje: */
1.
2.
    2.1
    2.2
3.

I funkce counters() může používat formát a to tak, že ho uvedete jako třetí parametr:

span:before {
    content: counters(spans, '-', upper-latin); 
}

/* Vygeneruje např.: */
...
C-X
   C-X-A
   C-X-B

Počítat či nepočítat

Jak jsem již psal, counter se zvyšuje vždy při vykreslení prvku, což znamená, že pokud se prvek nevykreslí, nezvýší ani counter:

.get-1-point { counter-increment: points; }
.get-1-point.disabled { display: none; }
/* disabled prvky nebudou zvyšovat skóre */

Naproti tomu i neviditelné prvky se stále musí „vykreslovat“ (prohlížeč musí zjistit, jak budou velké, aby pro ně rezervoval místo), takže použití vlastnosti visibility:hidden nebo opacity:0 zajistí započtení prvku (pomocí position:absolute můžete zajistit, aby takové prvky nerozbíjely strukturu stránky):

.get-1-point.hidden {
    opacity: 0;
} /* není vidět, ale stále s ním lze pracovat */
.bonus-points {
    counter-increment: points 50;
    visibility: hidden;
    position: absolute;
} /* není ve stránce, ale stále se počítá */

Počítat, ale kolikrát?

Při vykreslování se počítá i s fiktivními prvky :before a :after. Pokud tedy nadefinujete counter-increment jak do prvku tak i jeho :before a/nebo :after, bude započítán vícekrát. Použít se to dá i k různým způsobům, jak prvek započít:

body { counter-reset: odrazky 0; }
.odrazka:before {
    counter-increment: odrazky;
    content: counter(odrazky);
}

/* tento zápis je ekvivalentní: */
body { counter-reset: odrazky 1; }
.odrazka:before { content: counter(odrazky); }
.odrazka { counter-increment: odrazky; }

A použít se to dá i k tomu, když má prvek zvýšit různé countery a nechcete řešit problém s přepisováním pravidel:

/* prvek .get.add-1-point zvýší pouze points,
   ale ne collected -> problém */
.get { counter-increment: collected; }
.get.add-1-point { counter-increment: points; }

/* Tohle už bude fungovat správně: */
.get:after { counter-increment: collected; }
.get.add-1-points { counter-increment: points 1 }
.get.add-2-points { counter-increment: points 2 }

Další zdroje:

Napsat komentář

Vaše emailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *